otto 2.0.0.pre1 → 2.0.0.pre2

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -1
  3. data/.github/workflows/claude-code-review.yml +1 -1
  4. data/.github/workflows/claude.yml +1 -1
  5. data/.rubocop.yml +4 -1
  6. data/CHANGELOG.rst +54 -6
  7. data/Gemfile +1 -1
  8. data/Gemfile.lock +19 -18
  9. data/docs/.gitignore +1 -0
  10. data/docs/migrating/v2.0.0-pre2.md +345 -0
  11. data/lib/otto/core/configuration.rb +2 -2
  12. data/lib/otto/core/middleware_stack.rb +80 -0
  13. data/lib/otto/core/router.rb +7 -6
  14. data/lib/otto/env_keys.rb +114 -0
  15. data/lib/otto/helpers/base.rb +2 -21
  16. data/lib/otto/helpers/response.rb +22 -0
  17. data/lib/otto/mcp/{validation.rb → schema_validation.rb} +3 -2
  18. data/lib/otto/mcp/server.rb +26 -13
  19. data/lib/otto/response_handlers/json.rb +6 -0
  20. data/lib/otto/route.rb +44 -48
  21. data/lib/otto/route_handlers/factory.rb +22 -9
  22. data/lib/otto/security/authentication/authentication_middleware.rb +29 -12
  23. data/lib/otto/security/authentication/failure_result.rb +15 -7
  24. data/lib/otto/security/authentication/route_auth_wrapper.rb +149 -0
  25. data/lib/otto/security/authentication/strategies/{public_strategy.rb → noauth_strategy.rb} +1 -1
  26. data/lib/otto/security/authentication/strategy_result.rb +129 -15
  27. data/lib/otto/security/authentication.rb +2 -2
  28. data/lib/otto/security/config.rb +0 -11
  29. data/lib/otto/security/configurator.rb +2 -2
  30. data/lib/otto/security/middleware/rate_limit_middleware.rb +19 -3
  31. data/lib/otto/version.rb +1 -1
  32. data/lib/otto.rb +2 -3
  33. data/otto.gemspec +2 -0
  34. metadata +26 -6
  35. data/changelog.d/20250911_235619_delano_next.rst +0 -28
  36. data/changelog.d/20250912_123055_delano_remove_ostruct.rst +0 -21
  37. data/changelog.d/20250912_175625_claude_delano_remove_ostruct.rst +0 -21
@@ -7,26 +7,34 @@ class Otto
7
7
  module Authentication
8
8
  # Failure result for authentication failures
9
9
  FailureResult = Data.define(:failure_reason, :auth_method) do
10
- def success?
11
- false
12
- end
13
-
14
- def failure?
15
- true
16
- end
10
+ # FailureResult represents authentication failure
11
+ # Returned by strategies when authentication fails
12
+ # Contains failure reason for error messages
17
13
 
14
+ # Check if authenticated - always false for failures
15
+ #
16
+ # @return [Boolean] False (failures are never authenticated)
18
17
  def authenticated?
19
18
  false
20
19
  end
21
20
 
21
+ # Check if anonymous - always true for failures
22
+ #
23
+ # @return [Boolean] True (failures have no user)
22
24
  def anonymous?
23
25
  true
24
26
  end
25
27
 
28
+ # Get empty user context for failures
29
+ #
30
+ # @return [Hash] Empty hash
26
31
  def user_context
27
32
  {}
28
33
  end
29
34
 
35
+ # Create a string representation for debugging
36
+ #
37
+ # @return [String] Debug representation
30
38
  def inspect
31
39
  "#<FailureResult reason=#{failure_reason.inspect} method=#{auth_method}>"
32
40
  end
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/otto/security/authentication/route_auth_wrapper.rb
4
+
5
+ class Otto
6
+ module Security
7
+ module Authentication
8
+ # Wraps route handlers to enforce authentication requirements
9
+ #
10
+ # This wrapper executes authentication strategies AFTER routing but BEFORE
11
+ # route handler execution. This solves the architectural issue where
12
+ # middleware-based authentication runs before routing (so can't access route info).
13
+ #
14
+ # Flow:
15
+ # 1. Route matched (route_definition available)
16
+ # 2. RouteAuthWrapper#call invoked
17
+ # 3. Execute auth strategy based on route's auth_requirement
18
+ # 4. Set env['otto.strategy_result'], env['otto.user']
19
+ # 5. If auth fails, return 401 or redirect
20
+ # 6. If auth succeeds, call wrapped handler
21
+ #
22
+ # @example
23
+ # handler = InstanceMethodHandler.new(route_def, otto)
24
+ # wrapped = RouteAuthWrapper.new(handler, route_def, auth_config)
25
+ # wrapped.call(env, extra_params)
26
+ #
27
+ class RouteAuthWrapper
28
+ attr_reader :wrapped_handler, :route_definition, :auth_config
29
+
30
+ def initialize(wrapped_handler, route_definition, auth_config)
31
+ @wrapped_handler = wrapped_handler
32
+ @route_definition = route_definition
33
+ @auth_config = auth_config # Hash: { auth_strategies: {}, default_auth_strategy: 'publicly' }
34
+ end
35
+
36
+ # Execute authentication then call wrapped handler
37
+ #
38
+ # @param env [Hash] Rack environment
39
+ # @param extra_params [Hash] Additional parameters
40
+ # @return [Array] Rack response array
41
+ def call(env, extra_params = {})
42
+ # Execute authentication strategy for this route
43
+ auth_requirement = route_definition.auth_requirement
44
+ strategy = get_strategy(auth_requirement)
45
+
46
+ unless strategy
47
+ Otto.logger.error "[RouteAuthWrapper] No strategy found for requirement: #{auth_requirement}"
48
+ return unauthorized_response(env, "Authentication strategy not configured")
49
+ end
50
+
51
+ # Execute the strategy
52
+ result = strategy.authenticate(env, auth_requirement)
53
+
54
+ # Set environment variables for controllers/logic
55
+ env['otto.strategy_result'] = result
56
+ env['otto.user'] = result.user if result.is_a?(StrategyResult)
57
+ env['otto.user_context'] = result.user_context if result.is_a?(StrategyResult)
58
+
59
+ # Handle authentication failure
60
+ if result.is_a?(FailureResult)
61
+ return auth_failure_response(env, result)
62
+ end
63
+
64
+ # Authentication succeeded - call wrapped handler
65
+ wrapped_handler.call(env, extra_params)
66
+ end
67
+
68
+ private
69
+
70
+ # Get strategy from auth_config hash
71
+ #
72
+ # @param requirement [String] Auth requirement from route
73
+ # @return [AuthStrategy, nil] Strategy instance or nil
74
+ def get_strategy(requirement)
75
+ return nil unless auth_config && auth_config[:auth_strategies]
76
+
77
+ auth_config[:auth_strategies][requirement]
78
+ end
79
+
80
+ # Generate 401 response for authentication failure
81
+ #
82
+ # @param env [Hash] Rack environment
83
+ # @param result [FailureResult] Failure result from strategy
84
+ # @return [Array] Rack response array
85
+ def auth_failure_response(env, result)
86
+ # Check if request wants JSON
87
+ accept_header = env['HTTP_ACCEPT'] || ''
88
+ wants_json = accept_header.include?('application/json')
89
+
90
+ if wants_json
91
+ json_auth_error(result)
92
+ else
93
+ html_auth_error(result)
94
+ end
95
+ end
96
+
97
+ # Generate JSON 401 response
98
+ #
99
+ # @param result [FailureResult] Failure result
100
+ # @return [Array] Rack response array
101
+ def json_auth_error(result)
102
+ body = {
103
+ error: 'Authentication Required',
104
+ message: result.failure_reason || 'Not authenticated',
105
+ timestamp: Time.now.to_i
106
+ }.to_json
107
+
108
+ [
109
+ 401,
110
+ { 'content-type' => 'application/json' },
111
+ [body]
112
+ ]
113
+ end
114
+
115
+ # Generate HTML 401 response or redirect
116
+ #
117
+ # @param result [FailureResult] Failure result
118
+ # @return [Array] Rack response array
119
+ def html_auth_error(result)
120
+ # For HTML requests, redirect to login
121
+ login_path = auth_config[:login_path] || '/signin'
122
+
123
+ [
124
+ 302,
125
+ { 'location' => login_path },
126
+ ["Redirecting to #{login_path}"]
127
+ ]
128
+ end
129
+
130
+ # Generate generic unauthorized response
131
+ #
132
+ # @param env [Hash] Rack environment
133
+ # @param message [String] Error message
134
+ # @return [Array] Rack response array
135
+ def unauthorized_response(env, message)
136
+ accept_header = env['HTTP_ACCEPT'] || ''
137
+ wants_json = accept_header.include?('application/json')
138
+
139
+ if wants_json
140
+ body = { error: message }.to_json
141
+ [401, { 'content-type' => 'application/json' }, [body]]
142
+ else
143
+ [401, { 'content-type' => 'text/plain' }, [message]]
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
@@ -8,7 +8,7 @@ class Otto
8
8
  module Authentication
9
9
  module Strategies
10
10
  # Public access strategy - always allows access
11
- class PublicStrategy < AuthStrategy
11
+ class NoAuthStrategy < AuthStrategy
12
12
  def authenticate(env, _requirement)
13
13
  Otto::Security::Authentication::StrategyResult.anonymous(metadata: { ip: env['REMOTE_ADDR'] })
14
14
  end
@@ -22,40 +22,142 @@ class Otto
22
22
  module Security
23
23
  module Authentication
24
24
  StrategyResult = Data.define(:session, :user, :auth_method, :metadata) do
25
+ # =====================================================================
26
+ # USAGE PATTERNS - READ THIS FIRST
27
+ # =====================================================================
28
+ #
29
+ # StrategyResult represents authentication state for a request.
30
+ # It serves TWO distinct purposes that must not be confused:
31
+ #
32
+ # 1. REQUEST STATE: Current session/user information
33
+ # - Use `authenticated?` to check if session has a user
34
+ # - Available on ALL requests (anonymous or authenticated)
35
+ #
36
+ # 2. AUTH ATTEMPT OUTCOME: Whether authentication just succeeded
37
+ # - Use `auth_attempt_succeeded?` to check if auth strategy ran
38
+ # - Only true when route had auth=... requirement AND succeeded
39
+ #
40
+ # CREATION PATTERNS
41
+ # -----------------
42
+ #
43
+ # StrategyResult should ONLY be created by:
44
+ #
45
+ # 1. Otto's AuthenticationMiddleware (automatic, route-based)
46
+ # - Routes WITH auth=...: Creates result from strategy execution
47
+ # - Routes WITHOUT auth=...: Creates anonymous result
48
+ #
49
+ # 2. Auth app router (manual, for Logic class compatibility)
50
+ # - Manually builds StrategyResult for Roda routes
51
+ # - Maintains same interface as Otto controllers
52
+ #
53
+ # APPLICATION CODE SHOULD NOT manually create StrategyResult!
54
+ # Instead, access session directly or rely on middleware.
55
+ #
56
+ # SESSION CONTRACT
57
+ # ----------------
58
+ #
59
+ # For multi-app architectures with shared session:
60
+ #
61
+ # Required session keys for authenticated state:
62
+ # session['authenticated'] # Boolean flag
63
+ # session['identity_id'] # User/customer ID
64
+ # session['authenticated_at'] # Timestamp
65
+ #
66
+ # Optional session keys:
67
+ # session['email'] # User email
68
+ # session['ip_address'] # Client IP
69
+ # session['user_agent'] # Client UA
70
+ # session['locale'] # User locale
71
+ #
72
+ # Advanced mode adds:
73
+ # session['account_external_id'] # Rodauth external_id
74
+ # session['advanced_account_id'] # Rodauth account ID
75
+ #
76
+ # EXAMPLES
77
+ # --------
78
+ #
79
+ # Check if user in session (registration flow):
80
+ # class CreateAccount
81
+ # def raise_concerns
82
+ # # Block registration if already logged in
83
+ # raise FormError, "Already signed up" if @context.authenticated?
84
+ # end
85
+ # end
86
+ #
87
+ # Check if auth just succeeded (post-login redirect):
88
+ # class LoginHandler
89
+ # def process
90
+ # if @context.auth_attempt_succeeded?
91
+ # redirect_to dashboard_path
92
+ # end
93
+ # end
94
+ # end
95
+ #
96
+ # Distinguish between the two:
97
+ # @context.authenticated? #=> true (user in session)
98
+ # @context.auth_attempt_succeeded? #=> false (no auth route)
99
+ #
100
+ # # vs route with auth=session:
101
+ # @context.authenticated? #=> true (user in session)
102
+ # @context.auth_attempt_succeeded? #=> true (strategy just ran)
103
+ #
104
+ # =====================================================================
105
+
25
106
  # Create an anonymous (unauthenticated) result
26
- # @return [StrategyResult] Anonymous result with empty session and nil user
107
+ #
108
+ # Used by middleware for routes without auth requirements
109
+ # and by PublicStrategy for publicly accessible routes.
110
+ #
111
+ # @param metadata [Hash] Optional metadata (IP, user agent, etc.)
112
+ # @return [StrategyResult] Anonymous result with nil user
27
113
  def self.anonymous(metadata: {})
28
114
  new(
29
115
  session: {},
30
- user: nil, # Changed from {} to nil - clearer semantics
116
+ user: nil,
31
117
  auth_method: 'anonymous',
32
118
  metadata: metadata
33
119
  )
34
120
  end
35
121
 
36
- # Check if the request is authenticated (has a user)
37
- # @return [Boolean] True if user is present, false otherwise
122
+ # Check if the request has an authenticated user in session
123
+ #
124
+ # This checks REQUEST STATE, not auth attempt outcome.
125
+ # Returns true if session contains a user, regardless of
126
+ # whether authentication just occurred or was from a previous request.
127
+ #
128
+ # @return [Boolean] True if user is present in session
129
+ # @example
130
+ # # Block registration if user already logged in
131
+ # raise FormError if @context.authenticated?
38
132
  def authenticated?
39
133
  !user.nil?
40
134
  end
41
135
 
42
- # Check if the request is anonymous (no user)
136
+ # Check if authentication strategy just executed and succeeded
137
+ #
138
+ # This checks AUTH ATTEMPT OUTCOME, not just session state.
139
+ # Returns true only when:
140
+ # 1. Route had an auth=... requirement (not anonymous/public)
141
+ # 2. Auth strategy executed
142
+ # 3. Authentication succeeded (user authenticated)
143
+ #
144
+ # @return [Boolean] True if auth strategy just succeeded
145
+ # @example
146
+ # # Redirect after successful login
147
+ # redirect_to dashboard if @context.auth_attempt_succeeded?
148
+ def auth_attempt_succeeded?
149
+ authenticated? && auth_method.to_s != 'anonymous'
150
+ end
151
+
152
+ # Check if the request is anonymous (no user in session)
153
+ #
43
154
  # @return [Boolean] True if not authenticated
44
155
  def anonymous?
45
156
  user.nil?
46
157
  end
47
158
 
48
- # Success/failure methods for compatibility
49
- def success?
50
- true # If we have a StrategyResult, authentication succeeded
51
- end
52
-
53
- def failure?
54
- false # Failures return nil, not a StrategyResult
55
- end
56
-
57
-
58
159
  # Check if the user has a specific role
160
+ #
59
161
  # @param role [String, Symbol] Role to check
60
162
  # @return [Boolean] True if user has the role
61
163
  def has_role?(role)
@@ -75,6 +177,7 @@ class Otto
75
177
  end
76
178
 
77
179
  # Check if the user has a specific permission
180
+ #
78
181
  # @param permission [String, Symbol] Permission to check
79
182
  # @return [Boolean] True if user has the permission
80
183
  def has_permission?(permission)
@@ -97,6 +200,7 @@ class Otto
97
200
  end
98
201
 
99
202
  # Check if the user has any of the specified roles
203
+ #
100
204
  # @param roles [Array<String, Symbol>] Roles to check
101
205
  # @return [Boolean] True if user has any of the roles
102
206
  def has_any_role?(*roles)
@@ -104,6 +208,7 @@ class Otto
104
208
  end
105
209
 
106
210
  # Check if the user has any of the specified permissions
211
+ #
107
212
  # @param permissions [Array<String, Symbol>] Permissions to check
108
213
  # @return [Boolean] True if user has any of the permissions
109
214
  def has_any_permission?(*permissions)
@@ -111,6 +216,7 @@ class Otto
111
216
  end
112
217
 
113
218
  # Get user ID from various possible locations
219
+ #
114
220
  # @return [String, Integer, nil] User ID or nil
115
221
  def user_id
116
222
  return nil unless authenticated?
@@ -126,6 +232,7 @@ class Otto
126
232
  end
127
233
 
128
234
  # Get user name from various possible locations
235
+ #
129
236
  # @return [String, nil] User name or nil
130
237
  def user_name
131
238
  return nil unless authenticated?
@@ -141,12 +248,14 @@ class Otto
141
248
  end
142
249
 
143
250
  # Get session ID from various possible locations
251
+ #
144
252
  # @return [String, nil] Session ID or nil
145
253
  def session_id
146
254
  session[:id] || session['id'] || session[:session_id] || session['session_id']
147
255
  end
148
256
 
149
257
  # Get all user roles as an array
258
+ #
150
259
  # @return [Array<String>] Array of roles (empty if none)
151
260
  def roles
152
261
  return [] unless authenticated?
@@ -163,6 +272,7 @@ class Otto
163
272
  end
164
273
 
165
274
  # Get all user permissions as an array
275
+ #
166
276
  # @return [Array<String>] Array of permissions (empty if none)
167
277
  def permissions
168
278
  return [] unless authenticated?
@@ -173,6 +283,7 @@ class Otto
173
283
  end
174
284
 
175
285
  # Create a string representation for debugging
286
+ #
176
287
  # @return [String] Debug representation
177
288
  def inspect
178
289
  if authenticated?
@@ -183,6 +294,7 @@ class Otto
183
294
  end
184
295
 
185
296
  # Get user context - a hash containing user-specific information and metadata
297
+ #
186
298
  # @return [Hash] User context hash
187
299
  def user_context
188
300
  if authenticated?
@@ -203,6 +315,7 @@ class Otto
203
315
  end
204
316
 
205
317
  # Create a hash representation
318
+ #
206
319
  # @return [Hash] Hash representation of the context
207
320
  def to_h
208
321
  {
@@ -211,6 +324,7 @@ class Otto
211
324
  auth_method: auth_method,
212
325
  metadata: metadata,
213
326
  authenticated: authenticated?,
327
+ auth_attempt_succeeded: auth_attempt_succeeded?,
214
328
  user_id: user_id,
215
329
  user_name: user_name,
216
330
  roles: roles,
@@ -11,7 +11,7 @@ require_relative 'authentication/failure_result'
11
11
  require_relative 'authentication/authentication_middleware'
12
12
 
13
13
  # Load all strategies
14
- require_relative 'authentication/strategies/public_strategy'
14
+ require_relative 'authentication/strategies/noauth_strategy'
15
15
  require_relative 'authentication/strategies/session_strategy'
16
16
  require_relative 'authentication/strategies/role_strategy'
17
17
  require_relative 'authentication/strategies/api_key_strategy'
@@ -21,7 +21,7 @@ class Otto
21
21
  module Security
22
22
  # Backward compatibility aliases for the old namespace
23
23
  AuthStrategy = Authentication::AuthStrategy
24
- PublicStrategy = Authentication::Strategies::PublicStrategy
24
+ NoAuthStrategy = Authentication::Strategies::NoAuthStrategy
25
25
  SessionStrategy = Authentication::Strategies::SessionStrategy
26
26
  RoleStrategy = Authentication::Strategies::RoleStrategy
27
27
  APIKeyStrategy = Authentication::Strategies::APIKeyStrategy
@@ -149,11 +149,6 @@ class Otto
149
149
  signature = Digest::SHA256.hexdigest(hash_input)
150
150
  csrf_token = "#{token}:#{signature}"
151
151
 
152
- puts '=== CSRF Generation ==='
153
- puts "hash_input: #{hash_input.inspect}"
154
- puts "signature: #{signature}"
155
- puts "csrf_token: #{csrf_token}"
156
-
157
152
  csrf_token
158
153
  end
159
154
 
@@ -168,12 +163,6 @@ class Otto
168
163
  expected_signature = Digest::SHA256.hexdigest(hash_input)
169
164
  comparison_result = secure_compare(signature, expected_signature)
170
165
 
171
- puts '=== CSRF Verification ==='
172
- puts "hash_input: #{hash_input.inspect}"
173
- puts "received_signature: #{signature}"
174
- puts "expected_signature: #{expected_signature}"
175
- puts "match: #{comparison_result}"
176
-
177
166
  comparison_result
178
167
  end
179
168
 
@@ -21,7 +21,7 @@ class Otto
21
21
  @security_config = security_config
22
22
  @middleware_stack = middleware_stack
23
23
  # Use provided auth_config or initialize a new one
24
- @auth_config = auth_config || { auth_strategies: {}, default_auth_strategy: 'publicly' }
24
+ @auth_config = auth_config || { auth_strategies: {}, default_auth_strategy: 'noauth' }
25
25
  end
26
26
 
27
27
  # Unified security configuration method with sensible defaults
@@ -192,7 +192,7 @@ class Otto
192
192
  #
193
193
  # @param strategies [Hash] Hash mapping strategy names to strategy instances
194
194
  # @param default_strategy [String] Default strategy to use when none specified
195
- def configure_auth_strategies(strategies, default_strategy: 'publicly')
195
+ def configure_auth_strategies(strategies, default_strategy: 'noauth')
196
196
  # Merge new strategies with existing ones, preserving shared state
197
197
  @auth_config[:auth_strategies].merge!(strategies)
198
198
  @auth_config[:default_auth_strategy] = default_strategy
@@ -7,6 +7,21 @@ class Otto
7
7
  module Middleware
8
8
  # Middleware for applying rate limiting to HTTP requests
9
9
  class RateLimitMiddleware
10
+ # NOTE: This middleware is a CONFIGURATOR, not an enforcer.
11
+ #
12
+ # Actual rate limiting is performed by Rack::Attack globally via
13
+ # configure_rack_attack!. This middleware registers during initialization
14
+ # and then passes through all requests.
15
+ #
16
+ # To enforce rate limits, Rack::Attack must be added to the middleware
17
+ # stack BEFORE Otto's router (typically done by the hosting application).
18
+ #
19
+ # Example (config.ru):
20
+ # use Rack::Attack # Must come before Otto
21
+ # run otto
22
+ #
23
+ # The call method is a pass-through; rate limiting happens in Rack::Attack.
24
+
10
25
  def initialize(app, security_config = nil)
11
26
  @app = app
12
27
  @security_config = security_config
@@ -19,10 +34,11 @@ class Otto
19
34
  end
20
35
  end
21
36
 
37
+ # Pass-through call - actual rate limiting handled by Rack::Attack
38
+ #
39
+ # This middleware does not enforce limits itself. It configures
40
+ # Rack::Attack during initialization, then delegates all requests.
22
41
  def call(env)
23
- return @app.call(env) unless @rate_limiter_available
24
-
25
- # Let rack-attack handle the rate limiting
26
42
  @app.call(env)
27
43
  end
28
44
 
data/lib/otto/version.rb CHANGED
@@ -3,5 +3,5 @@
3
3
  # lib/otto/version.rb
4
4
 
5
5
  class Otto
6
- VERSION = '2.0.0.pre1'
6
+ VERSION = '2.0.0.pre2'
7
7
  end
data/lib/otto.rb CHANGED
@@ -57,7 +57,6 @@ require_relative 'otto/utils'
57
57
  # otto.enable_frame_protection!
58
58
  #
59
59
  # Configuration Data class to replace OpenStruct
60
- # Configuration Data class to replace OpenStruct
61
60
  # Configuration class to replace OpenStruct
62
61
  class ConfigData
63
62
  def initialize(**kwargs)
@@ -311,7 +310,7 @@ class Otto
311
310
  # otto.add_auth_strategy('custom', MyCustomStrategy.new)
312
311
  def add_auth_strategy(name, strategy)
313
312
  # Ensure auth_config is initialized (handles edge case where it might be nil)
314
- @auth_config = { auth_strategies: {}, default_auth_strategy: 'publicly' } if @auth_config.nil?
313
+ @auth_config = { auth_strategies: {}, default_auth_strategy: 'noauth' } if @auth_config.nil?
315
314
 
316
315
  @auth_config[:auth_strategies][name] = strategy
317
316
 
@@ -349,7 +348,7 @@ class Otto
349
348
  @security_config = Otto::Security::Config.new
350
349
  @middleware = Otto::Core::MiddlewareStack.new
351
350
  # Initialize @auth_config first so it can be shared with the configurator
352
- @auth_config = { auth_strategies: {}, default_auth_strategy: 'publicly' }
351
+ @auth_config = { auth_strategies: {}, default_auth_strategy: 'noauth' }
353
352
  @security = Otto::Security::Configurator.new(@security_config, @middleware, @auth_config)
354
353
  end
355
354
 
data/otto.gemspec CHANGED
@@ -16,6 +16,8 @@ Gem::Specification.new do |spec|
16
16
 
17
17
  spec.required_ruby_version = ['>= 3.2', '< 4.0']
18
18
 
19
+ # Logger is not part of the default gems as of Ruby 3.5.0
20
+ spec.add_dependency 'logger', '~> 1', '< 2.0'
19
21
 
20
22
  spec.add_dependency 'rack', '~> 3.1', '< 4.0'
21
23
  spec.add_dependency 'rack-parser', '~> 0.7'
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.pre1
4
+ version: 2.0.0.pre2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delano Mandelbaum
@@ -9,6 +9,26 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: logger
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '1'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '2.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - "~>"
27
+ - !ruby/object:Gem::Version
28
+ version: '1'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '2.0'
12
32
  - !ruby/object:Gem::Dependency
13
33
  name: rack
14
34
  requirement: !ruby/object:Gem::Requirement
@@ -107,13 +127,11 @@ files:
107
127
  - LICENSE.txt
108
128
  - README.md
109
129
  - bin/rspec
110
- - changelog.d/20250911_235619_delano_next.rst
111
- - changelog.d/20250912_123055_delano_remove_ostruct.rst
112
- - changelog.d/20250912_175625_claude_delano_remove_ostruct.rst
113
130
  - changelog.d/README.md
114
131
  - changelog.d/scriv.ini
115
132
  - docs/.gitignore
116
133
  - docs/migrating/v2.0.0-pre1.md
134
+ - docs/migrating/v2.0.0-pre2.md
117
135
  - examples/.gitignore
118
136
  - examples/advanced_routes/README.md
119
137
  - examples/advanced_routes/app.rb
@@ -175,6 +193,7 @@ files:
175
193
  - lib/otto/core/router.rb
176
194
  - lib/otto/core/uri_generator.rb
177
195
  - lib/otto/design_system.rb
196
+ - lib/otto/env_keys.rb
178
197
  - lib/otto/helpers/base.rb
179
198
  - lib/otto/helpers/request.rb
180
199
  - lib/otto/helpers/response.rb
@@ -184,8 +203,8 @@ files:
184
203
  - lib/otto/mcp/rate_limiting.rb
185
204
  - lib/otto/mcp/registry.rb
186
205
  - lib/otto/mcp/route_parser.rb
206
+ - lib/otto/mcp/schema_validation.rb
187
207
  - lib/otto/mcp/server.rb
188
- - lib/otto/mcp/validation.rb
189
208
  - lib/otto/response_handlers.rb
190
209
  - lib/otto/response_handlers/auto.rb
191
210
  - lib/otto/response_handlers/base.rb
@@ -207,9 +226,10 @@ files:
207
226
  - lib/otto/security/authentication/auth_strategy.rb
208
227
  - lib/otto/security/authentication/authentication_middleware.rb
209
228
  - lib/otto/security/authentication/failure_result.rb
229
+ - lib/otto/security/authentication/route_auth_wrapper.rb
210
230
  - lib/otto/security/authentication/strategies/api_key_strategy.rb
231
+ - lib/otto/security/authentication/strategies/noauth_strategy.rb
211
232
  - lib/otto/security/authentication/strategies/permission_strategy.rb
212
- - lib/otto/security/authentication/strategies/public_strategy.rb
213
233
  - lib/otto/security/authentication/strategies/role_strategy.rb
214
234
  - lib/otto/security/authentication/strategies/session_strategy.rb
215
235
  - lib/otto/security/authentication/strategy_result.rb