otto 2.0.0.pre8 → 2.0.0.pre10

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 (53) 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/claude.yml +1 -1
  5. data/.github/workflows/code-smells.yml +2 -2
  6. data/CHANGELOG.rst +73 -35
  7. data/CLAUDE.md +44 -0
  8. data/Gemfile.lock +7 -7
  9. data/README.md +20 -0
  10. data/docs/.gitignore +2 -0
  11. data/docs/modern-authentication-authorization-landscape.md +558 -0
  12. data/docs/multi-strategy-authentication-design.md +1401 -0
  13. data/lib/otto/core/error_handler.rb +104 -10
  14. data/lib/otto/core/freezable.rb +0 -2
  15. data/lib/otto/core/helper_registry.rb +135 -0
  16. data/lib/otto/core/lifecycle_hooks.rb +63 -0
  17. data/lib/otto/core/middleware_management.rb +70 -0
  18. data/lib/otto/core/middleware_stack.rb +12 -8
  19. data/lib/otto/core/router.rb +25 -31
  20. data/lib/otto/core.rb +3 -0
  21. data/lib/otto/errors.rb +92 -0
  22. data/lib/otto/helpers.rb +2 -2
  23. data/lib/otto/locale/middleware.rb +1 -1
  24. data/lib/otto/mcp/core.rb +33 -0
  25. data/lib/otto/mcp/protocol.rb +1 -1
  26. data/lib/otto/mcp/rate_limiting.rb +6 -2
  27. data/lib/otto/mcp/schema_validation.rb +2 -2
  28. data/lib/otto/mcp.rb +1 -0
  29. data/lib/otto/privacy/core.rb +82 -0
  30. data/lib/otto/privacy.rb +1 -0
  31. data/lib/otto/{helpers/request.rb → request.rb} +17 -6
  32. data/lib/otto/{helpers/response.rb → response.rb} +20 -7
  33. data/lib/otto/response_handlers/json.rb +1 -3
  34. data/lib/otto/response_handlers/view.rb +1 -1
  35. data/lib/otto/route.rb +2 -4
  36. data/lib/otto/route_handlers/base.rb +88 -5
  37. data/lib/otto/route_handlers/class_method.rb +9 -67
  38. data/lib/otto/route_handlers/instance_method.rb +10 -57
  39. data/lib/otto/route_handlers/lambda.rb +2 -2
  40. data/lib/otto/route_handlers/logic_class.rb +85 -90
  41. data/lib/otto/security/authentication/auth_strategy.rb +2 -2
  42. data/lib/otto/security/authentication/strategies/api_key_strategy.rb +1 -1
  43. data/lib/otto/security/authentication/strategy_result.rb +9 -9
  44. data/lib/otto/security/authorization_error.rb +1 -1
  45. data/lib/otto/security/config.rb +3 -3
  46. data/lib/otto/security/core.rb +167 -0
  47. data/lib/otto/security/middleware/csrf_middleware.rb +1 -1
  48. data/lib/otto/security/middleware/validation_middleware.rb +1 -1
  49. data/lib/otto/security/rate_limiter.rb +7 -3
  50. data/lib/otto/security.rb +1 -0
  51. data/lib/otto/version.rb +1 -1
  52. data/lib/otto.rb +29 -404
  53. metadata +12 -3
data/lib/otto.rb CHANGED
@@ -11,12 +11,15 @@ require 'rack/request'
11
11
  require 'rack/response'
12
12
  require 'rack/utils'
13
13
 
14
+ require_relative 'otto/request'
15
+ require_relative 'otto/response'
14
16
  require_relative 'otto/route_definition'
15
17
  require_relative 'otto/route'
16
18
  require_relative 'otto/static'
17
19
  require_relative 'otto/helpers'
18
20
  require_relative 'otto/response_handlers'
19
21
  require_relative 'otto/route_handlers'
22
+ require_relative 'otto/errors'
20
23
  require_relative 'otto/locale'
21
24
  require_relative 'otto/mcp'
22
25
  require_relative 'otto/core'
@@ -53,6 +56,12 @@ class Otto
53
56
  include Otto::Core::Configuration
54
57
  include Otto::Core::ErrorHandler
55
58
  include Otto::Core::UriGenerator
59
+ include Otto::Core::HelperRegistry
60
+ include Otto::Core::MiddlewareManagement
61
+ include Otto::Core::LifecycleHooks
62
+ include Otto::Security::Core
63
+ include Otto::Privacy::Core
64
+ include Otto::MCP::Core
56
65
 
57
66
  LIB_HOME = __dir__ unless defined?(Otto::LIB_HOME)
58
67
 
@@ -67,7 +76,7 @@ class Otto
67
76
  attr_reader :routes, :routes_literal, :routes_static, :route_definitions, :option,
68
77
  :static_route, :security_config, :locale_config, :auth_config,
69
78
  :route_handler_factory, :mcp_server, :security, :middleware,
70
- :error_handlers
79
+ :error_handlers, :request_class, :response_class
71
80
  attr_accessor :not_found, :server_error
72
81
 
73
82
  def initialize(path = nil, opts = {})
@@ -79,9 +88,10 @@ class Otto
79
88
  load(path) unless path.nil?
80
89
  super()
81
90
 
82
- # Auto-register AuthorizationError for 403 Forbidden responses
83
- # This allows Logic classes to raise AuthorizationError for resource-level access control
84
- register_error_handler(Otto::Security::AuthorizationError, status: 403, log_level: :warn)
91
+ # Auto-register all Otto framework error classes
92
+ # This allows Logic classes and framework code to raise appropriate errors
93
+ # without requiring manual registration in implementing projects
94
+ register_framework_errors
85
95
 
86
96
  # Build the middleware app once after all initialization is complete
87
97
  build_app!
@@ -114,7 +124,7 @@ class Otto
114
124
 
115
125
  # Track request timing for lifecycle hooks
116
126
  start_time = Otto::Utils.now_in_μs
117
- request = Rack::Request.new(env)
127
+ request = @request_class.new(env)
118
128
  response_raw = nil
119
129
 
120
130
  begin
@@ -127,9 +137,9 @@ class Otto
127
137
  unless @request_complete_callbacks.empty?
128
138
  begin
129
139
  duration = Otto::Utils.now_in_μs - start_time
130
- # Wrap response tuple in Rack::Response for developer-friendly API
131
- # Otto's hook API should provide nice abstractions like Rack::Request/Response
132
- response = Rack::Response.new(response_raw[2], response_raw[0], response_raw[1])
140
+ # Wrap response tuple in Otto::Response for developer-friendly API
141
+ # Otto's hook API should provide nice abstractions like Otto::Request/Response
142
+ response = @response_class.new(response_raw[2], response_raw[0], response_raw[1])
133
143
  @request_complete_callbacks.each do |callback|
134
144
  callback.call(request, response, duration)
135
145
  end
@@ -143,402 +153,8 @@ class Otto
143
153
  response_raw
144
154
  end
145
155
 
146
- # Builds the middleware application chain
147
- # Called once at initialization and whenever middleware stack changes
148
- #
149
- # IMPORTANT: If you have routes with auth requirements, you MUST add session
150
- # middleware to your middleware stack BEFORE Otto processes requests.
151
- #
152
- # Session middleware is required for RouteAuthWrapper to correctly persist
153
- # session changes during authentication. Common options include:
154
- # - Rack::Session::Cookie (requires rack-session gem)
155
- # - Rack::Session::Pool
156
- # - Rack::Session::Memcache
157
- # - Any Rack-compatible session middleware
158
- #
159
- # Example:
160
- # use Rack::Session::Cookie, secret: ENV['SESSION_SECRET']
161
- # otto = Otto.new('routes.txt')
162
- #
163
- def build_app!
164
- base_app = method(:handle_request)
165
- @app = @middleware.wrap(base_app, @security_config)
166
- end
167
-
168
- # Middleware Management
169
- def use(middleware, ...)
170
- ensure_not_frozen!
171
- @middleware.add(middleware, ...)
172
-
173
- # NOTE: If build_app! is triggered during a request (via use() or
174
- # middleware_stack=), the @app instance variable could be swapped
175
- # mid-request in a multi-threaded environment.
176
-
177
- build_app! if @app # Rebuild app if already initialized
178
- end
179
-
180
- # Compatibility method for existing tests
181
- def middleware_stack
182
- @middleware.middleware_list
183
- end
184
-
185
- # Compatibility method for existing tests
186
- def middleware_stack=(stack)
187
- @middleware.clear!
188
- Array(stack).each { |middleware| @middleware.add(middleware) }
189
- build_app! if @app # Rebuild app if already initialized
190
- end
191
-
192
- # Compatibility method for middleware detection
193
- def middleware_enabled?(middleware_class)
194
- @middleware.includes?(middleware_class)
195
- end
196
-
197
- # Security Configuration Methods
198
-
199
- # Enable CSRF protection for POST, PUT, DELETE, and PATCH requests.
200
- # This will automatically add CSRF tokens to HTML forms and validate
201
- # them on unsafe HTTP methods.
202
- #
203
- # @example
204
- # otto.enable_csrf_protection!
205
- def enable_csrf_protection!
206
- ensure_not_frozen!
207
- return if @middleware.includes?(Otto::Security::Middleware::CSRFMiddleware)
208
-
209
- @security_config.enable_csrf_protection!
210
- use Otto::Security::Middleware::CSRFMiddleware
211
- end
212
-
213
- # Enable request validation including input sanitization, size limits,
214
- # and protection against XSS and SQL injection attacks.
215
- #
216
- # @example
217
- # otto.enable_request_validation!
218
- def enable_request_validation!
219
- ensure_not_frozen!
220
- return if @middleware.includes?(Otto::Security::Middleware::ValidationMiddleware)
221
-
222
- @security_config.input_validation = true
223
- use Otto::Security::Middleware::ValidationMiddleware
224
- end
225
-
226
- # Enable rate limiting to protect against abuse and DDoS attacks.
227
- # This will automatically add rate limiting rules based on client IP.
228
- #
229
- # @param options [Hash] Rate limiting configuration options
230
- # @option options [Integer] :requests_per_minute Maximum requests per minute per IP (default: 100)
231
- # @option options [Hash] :custom_rules Custom rate limiting rules
232
- # @example
233
- # otto.enable_rate_limiting!(requests_per_minute: 50)
234
- def enable_rate_limiting!(options = {})
235
- ensure_not_frozen!
236
- return if @middleware.includes?(Otto::Security::Middleware::RateLimitMiddleware)
237
-
238
- @security.configure_rate_limiting(options)
239
- use Otto::Security::Middleware::RateLimitMiddleware
240
- end
241
-
242
- # Add a custom rate limiting rule.
243
- #
244
- # @param name [String, Symbol] Rule name
245
- # @param options [Hash] Rule configuration
246
- # @option options [Integer] :limit Maximum requests
247
- # @option options [Integer] :period Time period in seconds (default: 60)
248
- # @option options [Proc] :condition Optional condition proc that receives request
249
- # @example
250
- # otto.add_rate_limit_rule('uploads', limit: 5, period: 300, condition: ->(req) { req.post? && req.path.include?('upload') })
251
- def add_rate_limit_rule(name, options)
252
- ensure_not_frozen!
253
- @security_config.rate_limiting_config[:custom_rules][name.to_s] = options
254
- end
255
-
256
- # Add a trusted proxy server for accurate client IP detection.
257
- # Only requests from trusted proxies will have their forwarded headers honored.
258
- #
259
- # @param proxy [String, Regexp] IP address, CIDR range, or regex pattern
260
- # @example
261
- # otto.add_trusted_proxy('10.0.0.0/8')
262
- # otto.add_trusted_proxy(/^172\.16\./)
263
- def add_trusted_proxy(proxy)
264
- ensure_not_frozen!
265
- @security_config.add_trusted_proxy(proxy)
266
- end
267
-
268
- # Set custom security headers that will be added to all responses.
269
- # These merge with the default security headers.
270
- #
271
- # @param headers [Hash] Hash of header name => value pairs
272
- # @example
273
- # otto.set_security_headers({
274
- # 'content-security-policy' => "default-src 'self'",
275
- # 'strict-transport-security' => 'max-age=31536000'
276
- # })
277
- def set_security_headers(headers)
278
- ensure_not_frozen!
279
- @security_config.security_headers.merge!(headers)
280
- end
281
-
282
- # Enable HTTP Strict Transport Security (HSTS) header.
283
- # WARNING: This can make your domain inaccessible if HTTPS is not properly
284
- # configured. Only enable this when you're certain HTTPS is working correctly.
285
- #
286
- # @param max_age [Integer] Maximum age in seconds (default: 1 year)
287
- # @param include_subdomains [Boolean] Apply to all subdomains (default: true)
288
- # @example
289
- # otto.enable_hsts!(max_age: 86400, include_subdomains: false)
290
- def enable_hsts!(max_age: 31_536_000, include_subdomains: true)
291
- ensure_not_frozen!
292
- @security_config.enable_hsts!(max_age: max_age, include_subdomains: include_subdomains)
293
- end
294
-
295
- # Enable Content Security Policy (CSP) header to prevent XSS attacks.
296
- # The default policy only allows resources from the same origin.
297
- #
298
- # @param policy [String] CSP policy string (default: "default-src 'self'")
299
- # @example
300
- # otto.enable_csp!("default-src 'self'; script-src 'self' 'unsafe-inline'")
301
- def enable_csp!(policy = "default-src 'self'")
302
- ensure_not_frozen!
303
- @security_config.enable_csp!(policy)
304
- end
305
-
306
- # Enable X-Frame-Options header to prevent clickjacking attacks.
307
- #
308
- # @param option [String] Frame options: 'DENY', 'SAMEORIGIN', or 'ALLOW-FROM uri'
309
- # @example
310
- # otto.enable_frame_protection!('DENY')
311
- def enable_frame_protection!(option = 'SAMEORIGIN')
312
- ensure_not_frozen!
313
- @security_config.enable_frame_protection!(option)
314
- end
315
-
316
- # Enable Content Security Policy (CSP) with nonce support for dynamic header generation.
317
- # This enables the res.send_csp_headers response helper method.
318
- #
319
- # @param debug [Boolean] Enable debug logging for CSP headers (default: false)
320
- # @example
321
- # otto.enable_csp_with_nonce!(debug: true)
322
- def enable_csp_with_nonce!(debug: false)
323
- ensure_not_frozen!
324
- @security_config.enable_csp_with_nonce!(debug: debug)
325
- end
326
-
327
- # Add a single authentication strategy
328
- #
329
- # @param name [String] Strategy name
330
- # @param strategy [Otto::Security::Authentication::AuthStrategy] Strategy instance
331
- # @example
332
- # otto.add_auth_strategy('custom', MyCustomStrategy.new)
333
- # Add an authentication strategy with a registered name
334
- #
335
- # This is the primary public API for registering authentication strategies.
336
- # The name you provide here will be available as `strategy_result.strategy_name`
337
- # in your application code, making it easy to identify which strategy authenticated
338
- # the current request.
339
- #
340
- # Also available via Otto::Security::Configurator for consolidated security config.
341
- #
342
- # @param name [String, Symbol] Strategy name (e.g., 'session', 'api_key', 'jwt')
343
- # @param strategy [AuthStrategy] Strategy instance
344
- # @example
345
- # otto.add_auth_strategy('session', SessionStrategy.new(session_key: 'user_id'))
346
- # otto.add_auth_strategy('api_key', APIKeyStrategy.new)
347
- # @raise [ArgumentError] if strategy name already registered
348
- def add_auth_strategy(name, strategy)
349
- ensure_not_frozen!
350
- # Ensure auth_config is initialized (handles edge case where it might be nil)
351
- @auth_config = { auth_strategies: {}, default_auth_strategy: 'noauth' } if @auth_config.nil?
352
-
353
- # Strict mode: Detect strategy name collisions
354
- if @auth_config[:auth_strategies].key?(name)
355
- raise ArgumentError, "Authentication strategy '#{name}' is already registered"
356
- end
357
-
358
- @auth_config[:auth_strategies][name] = strategy
359
- end
360
-
361
- # Register an error handler for expected business logic errors
362
- #
363
- # This allows you to handle known error conditions (like missing resources,
364
- # expired data, rate limits) without logging them as unhandled 500 errors.
365
- #
366
- # @param error_class [Class, String] The exception class or class name to handle
367
- # @param status [Integer] HTTP status code to return (default: 500)
368
- # @param log_level [Symbol] Log level for expected errors (:info, :warn, :error)
369
- # @param handler [Proc] Optional block to customize error response
370
- #
371
- # @example Basic usage with status code
372
- # otto.register_error_handler(Onetime::MissingSecret, status: 404, log_level: :info)
373
- # otto.register_error_handler(Onetime::SecretExpired, status: 410, log_level: :info)
374
- #
375
- # @example With custom response handler
376
- # otto.register_error_handler(Onetime::RateLimited, status: 429, log_level: :warn) do |error, req|
377
- # {
378
- # error: 'Rate limit exceeded',
379
- # retry_after: error.retry_after,
380
- # message: error.message
381
- # }
382
- # end
383
- #
384
- # @example Using string class names (for lazy loading)
385
- # otto.register_error_handler('Onetime::MissingSecret', status: 404, log_level: :info)
386
- #
387
- def register_error_handler(error_class, status: 500, log_level: :info, &handler)
388
- ensure_not_frozen!
389
-
390
- # Normalize error class to string for consistent lookup
391
- error_class_name = error_class.is_a?(String) ? error_class : error_class.name
392
-
393
- @error_handlers[error_class_name] = {
394
- status: status,
395
- log_level: log_level,
396
- handler: handler
397
- }
398
- end
399
-
400
- # Disable IP privacy to access original IP addresses
401
- #
402
- # IMPORTANT: By default, Otto masks public IP addresses for privacy.
403
- # Private/localhost IPs (127.0.0.0/8, 10.0.0.0/8, etc.) are never masked.
404
- # Only disable this if you need access to original public IPs.
405
- #
406
- # When disabled:
407
- # - env['REMOTE_ADDR'] contains the real IP address
408
- # - env['otto.original_ip'] also contains the real IP
409
- # - No PrivateFingerprint is created
410
- #
411
- # @example
412
- # otto.disable_ip_privacy!
413
- def disable_ip_privacy!
414
- ensure_not_frozen!
415
- @security_config.ip_privacy_config.disable!
416
- end
417
-
418
- # Enable full IP privacy (mask ALL IPs including private/localhost)
419
- #
420
- # By default, Otto exempts private and localhost IPs from masking for
421
- # better development experience. Call this method to mask ALL IPs
422
- # regardless of type.
423
- #
424
- # @example Enable full privacy (mask all IPs)
425
- # otto = Otto.new(routes_file)
426
- # otto.enable_full_ip_privacy!
427
- # # Now 127.0.0.1 → 127.0.0.0, 192.168.1.100 → 192.168.1.0
428
- #
429
- # @return [void]
430
- # @raise [FrozenError] if called after configuration is frozen
431
- def enable_full_ip_privacy!
432
- ensure_not_frozen!
433
- @security_config.ip_privacy_config.mask_private_ips = true
434
- end
435
-
436
- # Register a callback to be executed after each request completes
437
- #
438
- # Instance-level request completion callbacks allow each Otto instance
439
- # to have its own isolated set of callbacks, preventing duplicate
440
- # invocations in multi-app architectures (e.g., Rack::URLMap).
441
- #
442
- # The callback receives three arguments:
443
- # - request: Rack::Request object
444
- # - response: Rack::Response object (wrapping the response tuple)
445
- # - duration: Request processing duration in microseconds
446
- #
447
- # @example Basic usage
448
- # otto = Otto.new(routes_file)
449
- # otto.on_request_complete do |req, res, duration|
450
- # logger.info "Request completed", path: req.path, duration: duration
451
- # end
452
- #
453
- # @example Multi-app architecture
454
- # # App 1: Core Web Application
455
- # core_router = Otto.new
456
- # core_router.on_request_complete do |req, res, duration|
457
- # logger.info "Core app request", path: req.path
458
- # end
459
- #
460
- # # App 2: API Application
461
- # api_router = Otto.new
462
- # api_router.on_request_complete do |req, res, duration|
463
- # logger.info "API request", path: req.path
464
- # end
465
- #
466
- # # Each callback only fires for its respective Otto instance
467
- #
468
- # @yield [request, response, duration] Block to execute after each request
469
- # @yieldparam request [Rack::Request] The request object
470
- # @yieldparam response [Rack::Response] The response object
471
- # @yieldparam duration [Integer] Duration in microseconds
472
- # @return [self] Returns self for method chaining
473
- # @raise [FrozenError] if called after configuration is frozen
474
- def on_request_complete(&block)
475
- ensure_not_frozen!
476
- @request_complete_callbacks << block if block_given?
477
- self
478
- end
479
-
480
- # Get registered request completion callbacks (for internal use)
481
- #
482
- # @api private
483
- # @return [Array<Proc>] Array of registered callback blocks
484
- attr_reader :request_complete_callbacks
485
-
486
- # Configure IP privacy settings
487
- #
488
- # Privacy is enabled by default. Use this method to customize privacy
489
- # behavior without disabling it entirely.
490
- #
491
- # @param octet_precision [Integer] Number of octets to mask (1 or 2, default: 1)
492
- # @param hash_rotation [Integer] Seconds between key rotation (default: 86400)
493
- # @param geo [Boolean] Enable geo-location resolution (default: true)
494
- # @param redis [Redis] Redis connection for multi-server atomic key generation
495
- #
496
- # @example Mask 2 octets instead of 1
497
- # otto.configure_ip_privacy(octet_precision: 2)
498
- #
499
- # @example Disable geo-location
500
- # otto.configure_ip_privacy(geo: false)
501
- #
502
- # @example Custom hash rotation
503
- # otto.configure_ip_privacy(hash_rotation: 24.hours)
504
- #
505
- # @example Multi-server with Redis
506
- # redis = Redis.new(url: ENV['REDIS_URL'])
507
- # otto.configure_ip_privacy(redis: redis)
508
- def configure_ip_privacy(octet_precision: nil, hash_rotation: nil, geo: nil, redis: nil)
509
- ensure_not_frozen!
510
- config = @security_config.ip_privacy_config
511
-
512
- config.octet_precision = octet_precision if octet_precision
513
- config.hash_rotation_period = hash_rotation if hash_rotation
514
- config.geo_enabled = geo unless geo.nil?
515
- config.instance_variable_set(:@redis, redis) if redis
516
-
517
- # Validate configuration
518
- config.validate!
519
- end
520
-
521
- # Enable MCP (Model Context Protocol) server support
522
- #
523
- # @param options [Hash] MCP configuration options
524
- # @option options [Boolean] :http Enable HTTP endpoint (default: true)
525
- # @option options [Boolean] :stdio Enable STDIO communication (default: false)
526
- # @option options [String] :endpoint HTTP endpoint path (default: '/_mcp')
527
- # @example
528
- # otto.enable_mcp!(http: true, endpoint: '/api/mcp')
529
- def enable_mcp!(options = {})
530
- ensure_not_frozen!
531
- @mcp_server ||= Otto::MCP::Server.new(self)
532
-
533
- @mcp_server.enable!(options)
534
- Otto.logger.info '[MCP] Enabled MCP server' if Otto.debug
535
- end
536
-
537
- # Check if MCP is enabled
538
- # @return [Boolean]
539
- def mcp_enabled?
540
- @mcp_server&.enabled?
541
- end
156
+ # Security, Privacy, MCP, Middleware, HelperRegistry, and LifecycleHooks
157
+ # methods are provided by their respective Core modules (see includes above)
542
158
 
543
159
  private
544
160
 
@@ -556,6 +172,15 @@ class Otto
556
172
  @request_complete_callbacks = [] # Instance-level request completion callbacks
557
173
  @error_handlers = {} # Registered error handlers for expected errors
558
174
 
175
+ # Initialize helper module registries
176
+ @request_helper_modules = []
177
+ @response_helper_modules = []
178
+
179
+ # Finalize request/response classes with built-in helpers
180
+ # Custom helpers can be registered via register_request_helpers/register_response_helpers
181
+ # before first request (before configuration freezing)
182
+ finalize_request_response_classes
183
+
559
184
  # Add IP Privacy middleware first in stack (privacy by default for public IPs)
560
185
  # Private/localhost IPs are automatically exempted from masking
561
186
  @middleware.add_with_position(
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.pre8
4
+ version: 2.0.0.pre10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delano Mandelbaum
@@ -175,6 +175,8 @@ files:
175
175
  - docs/ipaddr-encoding-quirk.md
176
176
  - docs/migrating/v2.0.0-pre1.md
177
177
  - docs/migrating/v2.0.0-pre2.md
178
+ - docs/modern-authentication-authorization-landscape.md
179
+ - docs/multi-strategy-authentication-design.md
178
180
  - examples/.gitignore
179
181
  - examples/advanced_routes/README.md
180
182
  - examples/advanced_routes/app.rb
@@ -238,15 +240,17 @@ files:
238
240
  - lib/otto/core/error_handler.rb
239
241
  - lib/otto/core/file_safety.rb
240
242
  - lib/otto/core/freezable.rb
243
+ - lib/otto/core/helper_registry.rb
244
+ - lib/otto/core/lifecycle_hooks.rb
245
+ - lib/otto/core/middleware_management.rb
241
246
  - lib/otto/core/middleware_stack.rb
242
247
  - lib/otto/core/router.rb
243
248
  - lib/otto/core/uri_generator.rb
244
249
  - lib/otto/design_system.rb
245
250
  - lib/otto/env_keys.rb
251
+ - lib/otto/errors.rb
246
252
  - lib/otto/helpers.rb
247
253
  - lib/otto/helpers/base.rb
248
- - lib/otto/helpers/request.rb
249
- - lib/otto/helpers/response.rb
250
254
  - lib/otto/helpers/validation.rb
251
255
  - lib/otto/locale.rb
252
256
  - lib/otto/locale/config.rb
@@ -254,6 +258,7 @@ files:
254
258
  - lib/otto/logging_helpers.rb
255
259
  - lib/otto/mcp.rb
256
260
  - lib/otto/mcp/auth/token.rb
261
+ - lib/otto/mcp/core.rb
257
262
  - lib/otto/mcp/protocol.rb
258
263
  - lib/otto/mcp/rate_limiting.rb
259
264
  - lib/otto/mcp/registry.rb
@@ -262,9 +267,12 @@ files:
262
267
  - lib/otto/mcp/server.rb
263
268
  - lib/otto/privacy.rb
264
269
  - lib/otto/privacy/config.rb
270
+ - lib/otto/privacy/core.rb
265
271
  - lib/otto/privacy/geo_resolver.rb
266
272
  - lib/otto/privacy/ip_privacy.rb
267
273
  - lib/otto/privacy/redacted_fingerprint.rb
274
+ - lib/otto/request.rb
275
+ - lib/otto/response.rb
268
276
  - lib/otto/response_handlers.rb
269
277
  - lib/otto/response_handlers/auto.rb
270
278
  - lib/otto/response_handlers/base.rb
@@ -299,6 +307,7 @@ files:
299
307
  - lib/otto/security/authorization_error.rb
300
308
  - lib/otto/security/config.rb
301
309
  - lib/otto/security/configurator.rb
310
+ - lib/otto/security/core.rb
302
311
  - lib/otto/security/csrf.rb
303
312
  - lib/otto/security/middleware/csrf_middleware.rb
304
313
  - lib/otto/security/middleware/ip_privacy_middleware.rb