otto 2.9.0 → 2.10.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.
Files changed (96) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +5 -0
  3. data/.github/workflows/ci.yml +11 -8
  4. data/.github/workflows/claude-code-review.yml +38 -13
  5. data/.github/workflows/claude.yml +10 -8
  6. data/.github/workflows/code-smells.yml +5 -5
  7. data/.github/workflows/release-gem.yml +2 -2
  8. data/.github/workflows/ruby-lint.yml +3 -3
  9. data/.github/workflows/yardoc.yml +5 -5
  10. data/.gitignore +1 -5
  11. data/.rubocop_todo.yml +7 -5
  12. data/AGENTS.md +22 -1
  13. data/CHANGELOG.rst +203 -0
  14. data/Gemfile +3 -3
  15. data/Gemfile.lock +11 -15
  16. data/README.md +75 -37
  17. data/docs/README.md +166 -0
  18. data/docs/adr/README.md +16 -0
  19. data/docs/adr/adr-001-route-authentication-at-handler-boundary.md +38 -0
  20. data/docs/adr/adr-002-multi-strategy-authentication-and-authorization.md +50 -0
  21. data/docs/adr/adr-003-caddy-tls-route-based-integration.md +48 -0
  22. data/docs/adr/adr-004-separate-compatibility-from-security-maintenance.md +58 -0
  23. data/docs/guides/authentication.md +377 -0
  24. data/docs/guides/caddy-tls.md +205 -0
  25. data/docs/guides/configuration_freezing.md +146 -0
  26. data/docs/guides/enrichment.md +161 -0
  27. data/docs/guides/forwarded-authority.md +249 -0
  28. data/docs/guides/geo-country.md +168 -0
  29. data/docs/guides/ip_privacy.md +39 -0
  30. data/docs/guides/ipaddr-encoding-quirk.md +56 -0
  31. data/docs/guides/mcp.md +282 -0
  32. data/docs/guides/privacy.md +193 -0
  33. data/docs/guides/routing.md +181 -0
  34. data/docs/guides/structured_logging.md +281 -0
  35. data/docs/guides/testing-guide.md +391 -0
  36. data/docs/maintainers/github-actions.md +41 -0
  37. data/docs/maintainers/investigations/.gitignore +2 -0
  38. data/docs/migrating/v2.0.0.md +337 -0
  39. data/docs/reference/authentication.md +290 -0
  40. data/docs/reference/route-syntax.md +181 -0
  41. data/docs/reference/runtime-and-dependency-security.md +86 -0
  42. data/examples/advanced_routes/README.md +43 -57
  43. data/examples/authentication_strategies/README.md +37 -196
  44. data/examples/basic/README.md +24 -39
  45. data/examples/basic/config.ru +0 -1
  46. data/examples/caddy_tls_demo/README.md +8 -2
  47. data/examples/lambda_handlers/README.md +11 -2
  48. data/examples/mcp_demo/README.md +75 -161
  49. data/examples/mcp_demo/config.ru +1 -0
  50. data/examples/security_features/README.md +36 -234
  51. data/lib/otto/caddy_tls/localhost_guard.rb +22 -24
  52. data/lib/otto/core/configuration.rb +36 -22
  53. data/lib/otto/core/file_safety.rb +89 -31
  54. data/lib/otto/core/middleware_stack.rb +36 -36
  55. data/lib/otto/core/router.rb +62 -19
  56. data/lib/otto/env_keys.rb +20 -2
  57. data/lib/otto/mcp/auth/token.rb +10 -4
  58. data/lib/otto/mcp/core.rb +23 -5
  59. data/lib/otto/mcp/endpoint.rb +41 -0
  60. data/lib/otto/mcp/errors.rb +15 -0
  61. data/lib/otto/mcp/options.rb +292 -0
  62. data/lib/otto/mcp/protocol.rb +52 -22
  63. data/lib/otto/mcp/rate_limiting.rb +175 -97
  64. data/lib/otto/mcp/registry.rb +14 -14
  65. data/lib/otto/mcp/schema_validation.rb +20 -12
  66. data/lib/otto/mcp/server.rb +131 -32
  67. data/lib/otto/optional_dependency.rb +57 -0
  68. data/lib/otto/privacy/config.rb +10 -8
  69. data/lib/otto/security/authentication/route_auth_wrapper/role_authorization.rb +22 -5
  70. data/lib/otto/security/authentication/strategies/api_key_strategy.rb +181 -18
  71. data/lib/otto/security/authentication/strategies/permission_strategy.rb +1 -0
  72. data/lib/otto/security/authentication/strategies/role_strategy.rb +1 -0
  73. data/lib/otto/security/authentication/strategies/session_strategy.rb +1 -0
  74. data/lib/otto/security/authentication/strategy_result.rb +58 -28
  75. data/lib/otto/security/config.rb +328 -14
  76. data/lib/otto/security/configurator.rb +36 -6
  77. data/lib/otto/security/core.rb +19 -1
  78. data/lib/otto/security/middleware/ip_privacy_middleware.rb +105 -4
  79. data/lib/otto/security/middleware/rate_limit_middleware.rb +1 -6
  80. data/lib/otto/security/rate_limiter.rb +81 -46
  81. data/lib/otto/utils.rb +50 -0
  82. data/lib/otto/version.rb +1 -1
  83. data/lib/otto.rb +9 -11
  84. data/otto.gemspec +0 -2
  85. metadata +31 -41
  86. data/docs/.gitignore +0 -10
  87. data/docs/1108-STREAMING_ARCHITECTURE_ANALYSIS.md +0 -1105
  88. data/docs/1108-STREAMING_SUPPORT_SUMMARY.md +0 -376
  89. data/docs/enrichment.md +0 -128
  90. data/docs/geo-country.md +0 -181
  91. data/docs/ipaddr-encoding-quirk.md +0 -34
  92. data/docs/migrating/v2.0.0-pre1.md +0 -276
  93. data/docs/migrating/v2.0.0-pre2.md +0 -338
  94. data/docs/modern-authentication-authorization-landscape.md +0 -558
  95. data/docs/multi-strategy-authentication-design.md +0 -1401
  96. data/docs/reverse-proxy-network-services.md +0 -371
@@ -1,225 +1,66 @@
1
- # Otto - Authentication Strategies Example
1
+ # Otto Authentication Strategies Example
2
2
 
3
- This example demonstrates Otto's flexible authentication system with multiple strategies, token validation, and role-based access control.
3
+ This source-checkout example shows four route-level authentication strategies:
4
4
 
5
- ## What You'll Learn
5
+ - `authenticated` accepts `demo_token`.
6
+ - `role:admin` accepts `admin_token`.
7
+ - `permission:write` accepts `demo_token` or `admin_token`.
8
+ - `api_key` accepts `demo_api_key_123`.
6
9
 
7
- - How to configure multiple authentication strategies
8
- - Token-based authentication with session validation
9
- - API key authentication for programmatic access
10
- - Role and permission-based access control
11
- - How to protect routes with authentication requirements
12
- - Handling authentication failures and redirects
10
+ The strategies are registered in `app/auth.rb` before the application handles its first request. The protected routes are defined in `routes`.
13
11
 
14
- ## Structure
12
+ ## Prerequisites
15
13
 
16
- - `config.ru`: Rack configuration that initializes Otto and loads auth strategies
17
- - `routes`: Application routes with authentication requirements
18
- - `app/auth.rb`: Authentication strategy definitions and token setup
19
- - `app/controllers/`: Handler classes for protected and public routes
14
+ Run this example from an Otto source checkout. Its `config.ru` loads Otto from `../../lib`, rather than from an installed gem.
20
15
 
21
- ## Authentication Strategies in This Example
22
-
23
- ### Token-Based Auth
24
- Validates user tokens for web applications:
25
- ```
26
- GET /profile HomeController#profile auth=token
27
- ```
28
- Requires: `?token=demo_token`
29
-
30
- ### Admin Role Auth
31
- Validates admin-level access:
32
- ```
33
- GET /admin AdminController#dashboard auth=admin
34
- ```
35
- Requires: `?token=admin_token`
36
-
37
- ### Permission-Based Auth
38
- Validates specific permissions:
39
- ```
40
- POST /edit ArticleController#update auth=can_write
41
- ```
42
- Requires: `?token=demo_token` (with write permission)
43
-
44
- ### API Key Auth
45
- Validates API keys for programmatic access:
46
- ```
47
- GET /api/data ApiController#show auth=api_key
48
- ```
49
- Requires: `?api_key=demo_api_key_123`
50
-
51
- ## How to Run
52
-
53
- ### Using rackup (recommended)
16
+ You need a supported Ruby version and Bundler. `rackup` is a development dependency in the root `Gemfile`, so enable the `development` group before installing dependencies:
54
17
 
55
18
  ```sh
56
- cd examples/authentication_strategies
57
- rackup config.ru
19
+ # From the repository root
20
+ bundle config set with development
21
+ bundle install
58
22
  ```
59
23
 
60
- ### Using alternative servers
24
+ ## Run the example
61
25
 
62
26
  ```sh
63
- thin -R config.ru -p 9292 start
64
- puma config.ru -p 9292
27
+ # From the repository root
28
+ cd examples/authentication_strategies
29
+ bundle exec rackup config.ru
65
30
  ```
66
31
 
67
- Open your browser and navigate to `http://localhost:9292`.
68
-
69
- ## Testing Authentication
70
-
71
- ### Web Browser (Token-based)
32
+ The server listens on `http://127.0.0.1:9292` by default. Leave it running and use a second terminal for the checks below.
72
33
 
73
- Click these links or visit them directly:
34
+ ## Verify the configured credentials
74
35
 
75
- - **Public page**: [http://localhost:9292/](http://localhost:9292/)
76
- - **Authenticated user**: [http://localhost:9292/profile?token=demo_token](http://localhost:9292/profile?token=demo_token)
77
- - **Admin user**: [http://localhost:9292/admin?token=admin_token](http://localhost:9292/admin?token=admin_token)
78
- - **User with write permission**: [http://localhost:9292/edit?token=demo_token](http://localhost:9292/edit?token=demo_token)
79
-
80
- ### curl Commands (API Key)
36
+ The strategies read the token from the query string or the exact `Authorization` header value. The API-key strategy reads the key from the query string or `X-API-Key`:
81
37
 
82
38
  ```sh
83
- # Without API key (fails)
84
- curl http://localhost:9292/api/data
85
-
86
- # With API key (succeeds)
87
- curl "http://localhost:9292/api/data?api_key=demo_api_key_123"
39
+ curl -i "http://127.0.0.1:9292/profile?token=demo_token"
40
+ curl -i -H 'Authorization: demo_token' http://127.0.0.1:9292/profile
41
+ curl -i "http://127.0.0.1:9292/api/data?api_key=demo_api_key_123"
42
+ curl -i -H 'X-API-Key: demo_api_key_123' http://127.0.0.1:9292/api/data
88
43
  ```
89
44
 
90
- ### Testing Invalid Credentials
91
-
92
- Try accessing protected routes without valid credentials:
45
+ Use an omitted or invalid credential to exercise the authentication-failure path:
93
46
 
94
47
  ```sh
95
- # No token - redirects to login or returns 401
96
- curl http://localhost:9292/profile
97
-
98
- # Invalid token - returns 401
99
- curl "http://localhost:9292/profile?token=invalid"
100
-
101
- # Wrong token type - returns 401
102
- curl "http://localhost:9292/admin?api_key=demo_api_key_123"
103
- ```
104
-
105
- ## Expected Output
106
-
107
- ### Successful Authentication
108
- ```
109
- HTTP/1.1 200 OK
110
- Content-Type: text/html
111
-
112
- <h1>Welcome, alice!</h1>
113
- <p>This is your profile.</p>
48
+ curl -i http://127.0.0.1:9292/profile
49
+ curl -i "http://127.0.0.1:9292/api/data?api_key=invalid"
114
50
  ```
115
51
 
116
- ### Failed Authentication
117
- ```
118
- HTTP/1.1 401 Unauthorized
119
- Content-Type: text/plain
120
-
121
- Unauthorized
122
- ```
123
-
124
- ### Redirect to Login
125
- ```
126
- HTTP/1.1 302 Found
127
- Location: http://localhost:9292/?login=required
128
- ```
129
-
130
- ## File Structure Details
131
-
132
- ### Routes File
133
- - Public routes (no `auth=` requirement)
134
- - Protected routes with different auth strategies
135
- - Admin-only routes
136
- - API routes with API key authentication
137
-
138
- ### Auth Strategies (`app/auth.rb`)
139
- - Token validation logic with demo tokens
140
- - Admin role checking
141
- - Permission validation (read, write, admin)
142
- - API key validation for programmatic access
143
-
144
- ### Controllers (`app/controllers/`)
145
- - Welcome controller for public pages
146
- - Profile controller for authenticated users
147
- - Admin controller for admin-only pages
148
- - Article controller for permission-based access
149
- - API controller for programmatic access
150
-
151
- ## Key Concepts
152
-
153
- ### Strategy Registration
154
- Strategies are registered in `config.ru` before the first request:
155
-
156
- ```ruby
157
- app.add_auth_strategy('token', TokenStrategy.new)
158
- app.add_auth_strategy('admin', AdminStrategy.new)
159
- app.add_auth_strategy('api_key', APIKeyStrategy.new)
160
- ```
161
-
162
- ### Route Protection
163
- Routes specify their auth requirement in the routes file:
164
-
165
- ```
166
- GET /protected Controller#method auth=token
167
- POST /admin Controller#admin auth=admin
168
- ```
169
-
170
- ### User Context
171
- After successful authentication, `req.user_context` contains user info:
172
-
173
- ```ruby
174
- def profile
175
- user_id = @req.user_context[:user_id]
176
- @res.body = "Welcome, #{user_id}!"
177
- end
178
- ```
179
-
180
- ## Demo Credentials
181
-
182
- ### Tokens
183
- - `demo_token` - Regular user (Alice)
184
- - Permissions: read, write
185
- - Roles: user
186
- - `admin_token` - Administrator
187
- - Permissions: read, write, admin
188
- - Roles: admin, user
189
-
190
- ### API Keys
191
- - `demo_api_key_123` - Demo API access
192
- - Additional keys can be added to `app/auth.rb`
193
-
194
- ## Customizing Authentication
195
-
196
- To add your own authentication:
197
-
198
- 1. **Create a strategy class**:
199
- ```ruby
200
- class MyStrategy < Otto::Security::Authentication::AuthStrategy
201
- def authenticate(env, requirement)
202
- # Validate credentials
203
- success_result(user_id: 'alice') # or failure_result
204
- end
205
- end
206
- ```
207
-
208
- 2. **Register it in config.ru**:
209
- ```ruby
210
- app.add_auth_strategy('my_strategy', MyStrategy.new)
211
- ```
52
+ ### Current limitation
212
53
 
213
- 3. **Use it in routes**:
214
- ```
215
- GET /protected Controller#method auth=my_strategy
216
- ```
54
+ In this checkout, `app/controllers/main_controller.rb` and `app/controllers/auth_controller.rb` define class methods, while Otto instantiates route handlers with a request and response. Requests that reach those handlers therefore fail with an argument error. The commands above document the configured credential inputs, but they cannot currently verify a successful protected response until the controllers are made compatible with the handler interface.
217
55
 
218
- ## Next Steps
56
+ ## Files to inspect
219
57
 
220
- - Explore [Security Features](../security_features/) for CSRF, input validation, file uploads
221
- - Review [Advanced Routes](../advanced_routes/) for response types and logic classes
58
+ - `config.ru` creates the Otto application, enables CSRF and request validation, and registers the strategies.
59
+ - `app/auth.rb` contains the demo credentials and strategy callbacks.
60
+ - `routes` associates each protected route with its `auth=` requirement.
61
+ - `app/controllers/auth_controller.rb` supplies the protected responses.
222
62
 
223
- ## Further Reading
63
+ ## Next steps
224
64
 
225
- - [CLAUDE.md](../../CLAUDE.md#authentication-architecture) - Detailed auth documentation
65
+ - See the [security features example](../security_features/README.md) for CSRF and request-validation configuration.
66
+ - Read the [authentication guide](../../docs/guides/authentication.md) for application authentication design.
@@ -7,67 +7,52 @@ This example demonstrates a basic Otto application with a single route that acce
7
7
  - How to define routes in plain-text format
8
8
  - Creating a basic request handler class
9
9
  - Working with Rack request and response objects
10
- - Running an Otto application with different servers
11
- - Simple form handling and redirects
10
+ - Running an Otto application with Rackup
11
+ - Simple form handling and validation
12
12
 
13
- ## How to Run
13
+ ## Run it
14
14
 
15
- ### Using rackup (recommended)
15
+ ### Prerequisites
16
16
 
17
- ```sh
18
- cd examples/basic
19
- rackup config.ru -p 10770
20
- ```
21
-
22
- ### Using thin
17
+ Run this example from an Otto source checkout with Ruby 3.2 through 4.0 and Bundler. `rackup` is a development dependency in the root `Gemfile`, so enable that optional group before installing the bundle.
23
18
 
24
19
  ```sh
20
+ git clone https://github.com/delano/otto.git
21
+ cd otto
22
+ bundle config set with development
23
+ bundle install
25
24
  cd examples/basic
26
- thin -e dev -R config.ru -p 10770 start
25
+ bundle exec rackup config.ru -p 10770
27
26
  ```
28
27
 
29
- ### Using puma
28
+ The server listens on `http://localhost:10770`.
30
29
 
31
- ```sh
32
- cd examples/basic
33
- puma config.ru -p 10770
34
- ```
30
+ > **Current limitation:** The checked-in app starts, but `GET /` currently returns `500` because `App#index` calls the undefined `otto_form_wrapper` helper. Until that source issue is fixed, the browser workflow below cannot be completed.
35
31
 
36
- Open your browser and navigate to `http://localhost:10770`.
32
+ ### Verify after fixing the source issue
37
33
 
38
- ## Expected Output
34
+ In another terminal, confirm that the home page responds with HTML:
39
35
 
40
- ```
41
- Puma starting in single threaded mode...
42
- * Version 3.12.0 (ruby 3.2.0-p0), codename: Llama Litter Box
43
- * Min threads: 0, max threads: 32
44
- * Environment: development
45
- * Listening on tcp://127.0.0.1:10770
46
-
47
- [GET request to /]
48
- GET / 200 OK
49
-
50
- [Submitting feedback form]
51
- POST /feedback 302 Found
52
- Location: http://localhost:10770/
36
+ ```sh
37
+ curl -i http://localhost:10770/
53
38
  ```
54
39
 
55
- Then visit `http://localhost:10770` and submit feedback to see it in action.
40
+ Expect `HTTP/1.1 200` and an HTML page containing `Otto Framework`. Open the same URL in a browser, enter a message, and submit the form. The form posts to `/` (not `/feedback`) and displays either the submitted message or the validation message for an empty submission.
56
41
 
57
42
  ## File Structure
58
43
 
59
44
  * `README.md`: This file.
60
45
  * `app.rb`: Contains the application logic with two methods:
61
46
  - `index`: Displays the main page with a feedback form
62
- - `receive_feedback`: Handles form submissions and redirects back home
47
+ - `receive_feedback`: Handles form submissions and renders the result
63
48
  * `config.ru`: The Rack configuration file that loads Otto and the application.
64
- * `routes`: Defines the URL routes mapping to methods in the `App` class.
49
+ - `routes`: Maps `GET /` to `App#index` and `POST /` to `App#receive_feedback`.
65
50
 
66
- ## Trying It Out
51
+ ## Trying it out after resolving the limitation
67
52
 
68
- 1. **View the home page**: Open `http://localhost:10770` in your browser
69
- 2. **Submit feedback**: Enter text in the feedback form and click Submit
70
- 3. **Check the redirect**: You should be redirected back to the home page
53
+ 1. **View the home page**: Open `http://localhost:10770` in your browser.
54
+ 2. **Submit feedback**: Enter text in the feedback form and click **Send Feedback**.
55
+ 3. **Check the result**: The response shows the submitted message and a link back to the home page.
71
56
 
72
57
  ## Next Steps
73
58
 
@@ -77,4 +62,4 @@ Then visit `http://localhost:10770` and submit feedback to see it in action.
77
62
 
78
63
  ## Further Reading
79
64
 
80
- - [CLAUDE.md](../../CLAUDE.md) - Developer guidance and patterns
65
+ - [Project README](../../README.md) - Installation and framework overview
@@ -20,7 +20,6 @@ if Otto.env?(:dev)
20
20
  use Rack::CommonLogger
21
21
  use Rack::Reloader, 0
22
22
  app.option[:public] = public_path
23
- app.add_static_path '/favicon.ico'
24
23
  run app
25
24
  end
26
25
 
@@ -19,9 +19,15 @@ config-only on Caddy's side.
19
19
 
20
20
  ## Run it
21
21
 
22
+ This is a source-checkout example: its Rackup file loads Otto from `../../lib`.
23
+ From the repository root, install the optional development dependencies (which
24
+ include `rackup`) and start the app:
25
+
22
26
  ```sh
27
+ bundle config set with development
28
+ bundle install
23
29
  cd examples/caddy_tls_demo
24
- rackup config.ru # serves on http://localhost:9292
30
+ bundle exec rackup config.ru # serves on http://localhost:9292
25
31
  ```
26
32
 
27
33
  ## Try it
@@ -97,4 +103,4 @@ your real data over whatever channel your app already trusts. Two more defenses:
97
103
  respond @tls_permission 404
98
104
  ```
99
105
 
100
- See [`docs/reverse-proxy-network-services.md`](../../docs/reverse-proxy-network-services.md).
106
+ See [ADR-003: Caddy TLS route-based integration](../../docs/adr/adr-003-caddy-tls-route-based-integration.md).
@@ -89,11 +89,20 @@ anything.
89
89
 
90
90
  ## How to Run
91
91
 
92
+ Run this example from an Otto source checkout. It requires Ruby 3.2 through
93
+ 4.0, Bundler, and the development dependencies: `rackup` is a development
94
+ dependency in the root `Gemfile` and is not installed with the released `otto`
95
+ gem.
96
+
92
97
  ```sh
98
+ cd /path/to/otto
99
+ bundle config set with development
100
+ bundle install
93
101
  cd examples/lambda_handlers
94
- rackup config.ru -p 10780
102
+ bundle exec rackup config.ru -p 10780
95
103
  ```
96
104
 
105
+
97
106
  Then, from another terminal:
98
107
 
99
108
  ```sh
@@ -125,4 +134,4 @@ curl -X POST --data 'hello' localhost:10780/webhook
125
134
 
126
135
  ## Further Reading
127
136
 
128
- - [docs/ADVANCED_ROUTES.txt](../../docs/ADVANCED_ROUTES.txt)
137
+ - [Route syntax reference](../../docs/reference/route-syntax.md)