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
@@ -1,6 +1,6 @@
1
- # frozen_string_literal: true
2
-
3
1
  # lib/otto/response_handlers.rb
2
+ #
3
+ # frozen_string_literal: true
4
4
 
5
5
  class Otto
6
6
  module ResponseHandlers
data/lib/otto/route.rb CHANGED
@@ -1,6 +1,6 @@
1
- # frozen_string_literal: true
2
-
3
1
  # lib/otto/route.rb
2
+ #
3
+ # frozen_string_literal: true
4
4
 
5
5
  class Otto
6
6
  # Otto::Route
@@ -158,8 +158,8 @@ class Otto
158
158
  if response_type != 'default'
159
159
  context = {
160
160
  logic_instance: (kind == :instance ? inst : nil),
161
- status_code: nil,
162
- redirect_path: nil,
161
+ status_code: nil,
162
+ redirect_path: nil,
163
163
  }
164
164
 
165
165
  Otto::ResponseHandlers::HandlerFactory.handle_response(result, res, response_type, context)
@@ -1,6 +1,6 @@
1
- # frozen_string_literal: true
2
-
3
1
  # lib/otto/route_definition.rb
2
+ #
3
+ # frozen_string_literal: true
4
4
 
5
5
  class Otto
6
6
  # Immutable data class representing a complete route definition
@@ -73,10 +73,37 @@ class Otto
73
73
  @options.fetch(key.to_sym, default)
74
74
  end
75
75
 
76
- # Get authentication requirement
76
+ # Get authentication requirement (backward compatibility - returns first requirement)
77
77
  # @return [String, nil] The auth requirement or nil
78
78
  def auth_requirement
79
- option(:auth)
79
+ auth_requirements.first
80
+ end
81
+
82
+ # Get all authentication requirements as an array
83
+ # Supports multiple strategies: auth=session,apikey,oauth
84
+ # @return [Array<String>] Array of auth requirement strings
85
+ def auth_requirements
86
+ auth = option(:auth)
87
+ return [] unless auth
88
+
89
+ auth.split(',').map(&:strip).reject(&:empty?)
90
+ end
91
+
92
+ # Get role requirement for route-level authorization
93
+ # Supports single role or comma-separated roles (OR logic): role=admin,editor
94
+ # @return [String, nil] The role requirement or nil
95
+ def role_requirement
96
+ option(:role)
97
+ end
98
+
99
+ # Get all role requirements as an array
100
+ # Supports multiple roles with OR logic: role=admin,editor
101
+ # @return [Array<String>] Array of role requirement strings
102
+ def role_requirements
103
+ role = option(:role)
104
+ return [] unless role
105
+
106
+ role.split(',').map(&:strip).reject(&:empty?)
80
107
  end
81
108
 
82
109
  # Get response type
@@ -111,16 +138,16 @@ class Otto
111
138
  # @return [Hash]
112
139
  def to_h
113
140
  {
114
- verb: @verb,
115
- path: @path,
116
- definition: @definition,
117
- target: @target,
118
- klass_name: @klass_name,
141
+ verb: @verb,
142
+ path: @path,
143
+ definition: @definition,
144
+ target: @target,
145
+ klass_name: @klass_name,
119
146
  method_name: @method_name,
120
- kind: @kind,
121
- options: @options,
122
- pattern: @pattern,
123
- keys: @keys,
147
+ kind: @kind,
148
+ options: @options,
149
+ pattern: @pattern,
150
+ keys: @keys,
124
151
  }
125
152
  end
126
153
 
@@ -166,11 +193,11 @@ class Otto
166
193
  case target
167
194
  when /^(.+)\.(.+)$/
168
195
  # Class.method - call class method directly
169
- { klass_name: $1, method_name: $2, kind: :class }
196
+ { klass_name: ::Regexp.last_match(1), method_name: ::Regexp.last_match(2), kind: :class }
170
197
 
171
198
  when /^(.+)#(.+)$/
172
199
  # Class#method - instantiate then call instance method
173
- { klass_name: $1, method_name: $2, kind: :instance }
200
+ { klass_name: ::Regexp.last_match(1), method_name: ::Regexp.last_match(2), kind: :instance }
174
201
 
175
202
  when /^[A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*$/
176
203
  # Bare class name - instantiate the class
@@ -1,6 +1,7 @@
1
+ # lib/otto/route_handlers/base.rb
2
+ #
1
3
  # frozen_string_literal: true
2
4
 
3
- # lib/otto/route_handlers/base.rb
4
5
  require 'json'
5
6
 
6
7
  class Otto
@@ -1,6 +1,6 @@
1
- # frozen_string_literal: true
2
-
3
1
  # lib/otto/route_handlers/class_method.rb
2
+ #
3
+ # frozen_string_literal: true
4
4
 
5
5
  require 'json'
6
6
  require 'securerandom'
@@ -13,6 +13,7 @@ class Otto
13
13
  # Maintains backward compatibility for Controller.action patterns
14
14
  class ClassMethodHandler < BaseHandler
15
15
  def call(env, extra_params = {})
16
+ start_time = Otto::Utils.now_in_μs
16
17
  req = Rack::Request.new(env)
17
18
  res = Rack::Response.new
18
19
 
@@ -25,19 +26,22 @@ class Otto
25
26
 
26
27
  # Only handle response if response_type is not default
27
28
  if route_definition.response_type != 'default'
28
- handle_response(result, res, {
29
- class: target_class,
29
+ handle_response(result, res,
30
+ {
31
+ class: target_class,
30
32
  request: req,
31
- })
33
+ })
32
34
  end
33
35
  rescue StandardError => e
34
36
  # Check if we're being called through Otto's integrated context (vs direct handler testing)
35
37
  # In integrated context, let Otto's centralized error handler manage the response
36
38
  # In direct testing context, handle errors locally for unit testing
37
39
  if otto_instance
38
- # Log error for handler-specific context but let Otto's centralized error handler manage the response
39
- Otto.logger.error "[ClassMethodHandler] #{e.class}: #{e.message}"
40
- Otto.logger.debug "[ClassMethodHandler] Backtrace: #{e.backtrace.join("\n")}" if Otto.debug
40
+ # Store handler context in env for centralized error handler
41
+ handler_name = "#{target_class.name}##{route_definition.method_name}"
42
+ env['otto.handler'] = handler_name
43
+ env['otto.handler_duration'] = Otto::Utils.now_in_μs - start_time
44
+
41
45
  raise e # Re-raise to let Otto's centralized error handler manage the response
42
46
  else
43
47
  # Direct handler testing context - handle errors locally with security improvements
@@ -51,26 +55,15 @@ class Otto
51
55
  accept_header = env['HTTP_ACCEPT'].to_s
52
56
  if accept_header.include?('application/json')
53
57
  res.headers['content-type'] = 'application/json'
54
- error_data = if Otto.env?(:dev, :development)
55
- {
56
- error: 'Internal Server Error',
57
- message: 'Server error occurred. Check logs for details.',
58
- error_id: error_id,
59
- }
60
- else
61
- {
62
- error: 'Internal Server Error',
63
- message: 'An error occurred. Please try again later.',
64
- }
65
- end
58
+ error_data = {
59
+ error: 'Internal Server Error',
60
+ message: 'Server error occurred. Check logs for details.',
61
+ error_id: error_id,
62
+ }
66
63
  res.write JSON.generate(error_data)
67
64
  else
68
65
  res.headers['content-type'] = 'text/plain'
69
- if Otto.env?(:dev, :development)
70
- res.write "Server error (ID: #{error_id}). Check logs for details."
71
- else
72
- res.write 'An error occurred. Please try again later.'
73
- end
66
+ res.write "Server error (ID: #{error_id}). Check logs for details."
74
67
  end
75
68
 
76
69
  # Add security headers if available
@@ -1,8 +1,9 @@
1
- # frozen_string_literal: true
2
-
3
1
  # lib/otto/route_handlers/factory.rb
2
+ #
3
+ # frozen_string_literal: true
4
4
 
5
5
  require_relative 'base'
6
+ require_relative '../security/authentication/route_auth_wrapper'
6
7
 
7
8
  class Otto
8
9
  module RouteHandlers
@@ -14,24 +15,25 @@ class Otto
14
15
  # @return [BaseHandler] Appropriate handler for the route
15
16
  def self.create_handler(route_definition, otto_instance = nil)
16
17
  # Create base handler based on route kind
17
- handler = case route_definition.kind
18
- when :logic
19
- LogicClassHandler.new(route_definition, otto_instance)
20
- when :instance
21
- InstanceMethodHandler.new(route_definition, otto_instance)
22
- when :class
23
- ClassMethodHandler.new(route_definition, otto_instance)
24
- else
25
- raise ArgumentError, "Unknown handler kind: #{route_definition.kind}"
26
- end
18
+ handler_class = case route_definition.kind
19
+ when :logic then LogicClassHandler
20
+ when :instance then InstanceMethodHandler
21
+ when :class then ClassMethodHandler
22
+ else
23
+ raise ArgumentError, "Unknown handler kind: #{route_definition.kind}"
24
+ end
25
+
26
+ handler = handler_class.new(route_definition, otto_instance)
27
27
 
28
- # Wrap with auth enforcement if route has auth requirement
29
- if route_definition.auth_requirement && otto_instance&.auth_config
30
- require_relative '../security/authentication/route_auth_wrapper'
28
+ # Always wrap with RouteAuthWrapper to ensure env['otto.strategy_result'] is set
29
+ # - Routes WITH auth requirement: Enforces authentication
30
+ # - Routes WITHOUT auth requirement: Sets anonymous StrategyResult
31
+ if otto_instance&.auth_config
31
32
  handler = Otto::Security::Authentication::RouteAuthWrapper.new(
32
33
  handler,
33
34
  route_definition,
34
- otto_instance.auth_config
35
+ otto_instance.auth_config,
36
+ otto_instance.security_config
35
37
  )
36
38
  end
37
39
 
@@ -1,6 +1,6 @@
1
- # frozen_string_literal: true
2
-
3
1
  # lib/otto/route_handlers/instance_method.rb
2
+ #
3
+ # frozen_string_literal: true
4
4
  require 'securerandom'
5
5
 
6
6
  require_relative 'base'
@@ -11,6 +11,7 @@ class Otto
11
11
  # Maintains backward compatibility for Controller#action patterns
12
12
  class InstanceMethodHandler < BaseHandler
13
13
  def call(env, extra_params = {})
14
+ start_time = Otto::Utils.now_in_μs
14
15
  req = Rack::Request.new(env)
15
16
  res = Rack::Response.new
16
17
 
@@ -34,9 +35,11 @@ class Otto
34
35
  # In integrated context, let Otto's centralized error handler manage the response
35
36
  # In direct testing context, handle errors locally for unit testing
36
37
  if otto_instance
37
- # Log error for handler-specific context but let the centralized error handler manage the response
38
- Otto.logger.error "[InstanceMethodHandler] #{e.class}: #{e.message}"
39
- Otto.logger.debug "[InstanceMethodHandler] Backtrace: #{e.backtrace.join("\n")}" if Otto.debug
38
+ # Store handler context in env for centralized error handler
39
+ handler_name = "#{target_class}##{route_definition.method_name}"
40
+ env['otto.handler'] = handler_name
41
+ env['otto.handler_duration'] = Otto::Utils.now_in_μs - start_time
42
+
40
43
  raise e # Re-raise to let Otto's centralized error handler manage the response
41
44
  else
42
45
  # Direct handler testing context - handle errors locally with security improvements
@@ -1,6 +1,6 @@
1
- # frozen_string_literal: true
2
-
3
1
  # lib/otto/route_handlers/lambda.rb
2
+ #
3
+ # frozen_string_literal: true
4
4
  require 'securerandom'
5
5
 
6
6
  require_relative 'base'
@@ -10,6 +10,7 @@ class Otto
10
10
  # Custom handler for lambda/proc definitions (future extension)
11
11
  class LambdaHandler < BaseHandler
12
12
  def call(env, extra_params = {})
13
+ start_time = Otto::Utils.now_in_μs
13
14
  req = Rack::Request.new(env)
14
15
  res = Rack::Response.new
15
16
 
@@ -31,25 +32,12 @@ class Otto
31
32
  request: req,
32
33
  })
33
34
  rescue StandardError => e
34
- error_id = SecureRandom.hex(8)
35
- Otto.logger.error "[#{error_id}] #{e.class}: #{e.message}"
36
- Otto.logger.debug "[#{error_id}] Backtrace: #{e.backtrace.join("\n")}" if Otto.debug
37
-
38
- res.status = 500
39
- res.headers['content-type'] = 'text/plain'
35
+ # Store handler context in env for centralized error handler
36
+ handler_name = "Lambda[#{route_definition.klass_name}]"
37
+ env['otto.handler'] = handler_name
38
+ env['otto.handler_duration'] = Otto::Utils.now_in_μs - start_time
40
39
 
41
- if Otto.env?(:dev, :development)
42
- res.write "Lambda handler error (ID: #{error_id}). Check logs for details."
43
- else
44
- res.write 'An error occurred. Please try again later.'
45
- end
46
-
47
- # Add security headers if available
48
- if otto_instance.respond_to?(:security_config) && otto_instance.security_config
49
- otto_instance.security_config.security_headers.each do |header, value|
50
- res.headers[header] = value
51
- end
52
- end
40
+ raise e # Re-raise to let Otto's centralized error handler manage the response
53
41
  end
54
42
 
55
43
  res.finish
@@ -1,6 +1,6 @@
1
- # frozen_string_literal: true
2
-
3
1
  # lib/otto/route_handlers/logic_class.rb
2
+ #
3
+ # frozen_string_literal: true
4
4
  require 'json'
5
5
  require 'securerandom'
6
6
 
@@ -13,12 +13,13 @@ class Otto
13
13
  # Logic classes use signature: initialize(context, params, locale)
14
14
  class LogicClassHandler < BaseHandler
15
15
  def call(env, extra_params = {})
16
+ start_time = Otto::Utils.now_in_μs
16
17
  req = Rack::Request.new(env)
17
18
  res = Rack::Response.new
18
19
 
19
20
  begin
20
- # Get strategy result (guaranteed to exist from auth middleware)
21
- strategy_result = env['otto.strategy_result'] || Otto::Security::Authentication::StrategyResult.anonymous
21
+ # Get strategy result (guaranteed to exist from RouteAuthWrapper)
22
+ strategy_result = env['otto.strategy_result']
22
23
 
23
24
  # Initialize Logic class with new signature: context, params, locale
24
25
  logic_params = req.params.merge(extra_params)
@@ -30,7 +31,21 @@ class Otto
30
31
  json_data = JSON.parse(req.body.read)
31
32
  logic_params = logic_params.merge(json_data) if json_data.is_a?(Hash)
32
33
  rescue JSON::ParserError => e
33
- Otto.logger.error "[LogicClassHandler] JSON parsing error: #{e.message}"
34
+ # Base context pattern: create once, reuse for correlation
35
+ base_context = Otto::LoggingHelpers.request_context(env)
36
+
37
+ Otto.structured_log(:error, "JSON parsing error",
38
+ base_context.merge(
39
+ handler: "#{target_class}#call",
40
+ error: e.message,
41
+ error_class: e.class.name,
42
+ duration: Otto::Utils.now_in_μs - start_time
43
+ )
44
+ )
45
+
46
+ Otto::LoggingHelpers.log_backtrace(e,
47
+ base_context.merge(handler: "#{target_class}#call")
48
+ )
34
49
  end
35
50
  end
36
51
 
@@ -58,9 +73,11 @@ class Otto
58
73
  # In integrated context, let Otto's centralized error handler manage the response
59
74
  # In direct testing context, handle errors locally for unit testing
60
75
  if otto_instance
61
- # Log error for handler-specific context but let Otto's centralized error handler manage the response
62
- Otto.logger.error "[LogicClassHandler] #{e.class}: #{e.message}"
63
- Otto.logger.debug "[LogicClassHandler] Backtrace: #{e.backtrace.join("\n")}" if Otto.debug
76
+ # Store handler context in env for centralized error handler
77
+ handler_name = "#{target_class}#call"
78
+ env['otto.handler'] = handler_name
79
+ env['otto.handler_duration'] = Otto::Utils.now_in_μs - start_time
80
+
64
81
  raise e # Re-raise to let Otto's centralized error handler manage the response
65
82
  else
66
83
  # Direct handler testing context - handle errors locally with security improvements
@@ -1,6 +1,6 @@
1
- # frozen_string_literal: true
2
-
3
1
  # lib/otto/route_handlers.rb
2
+ #
3
+ # frozen_string_literal: true
4
4
 
5
5
  class Otto
6
6
  # Pluggable Route Handler Factory
@@ -1,13 +1,13 @@
1
+ # lib/otto/security/authentication/auth_failure.rb
2
+ #
1
3
  # frozen_string_literal: true
2
4
 
3
- # lib/otto/security/authentication/failure_result.rb
4
-
5
5
  class Otto
6
6
  module Security
7
7
  module Authentication
8
8
  # Failure result for authentication failures
9
- FailureResult = Data.define(:failure_reason, :auth_method) do
10
- # FailureResult represents authentication failure
9
+ AuthFailure = Data.define(:failure_reason, :auth_method) do
10
+ # AuthFailure represents authentication failure
11
11
  # Returned by strategies when authentication fails
12
12
  # Contains failure reason for error messages
13
13
 
@@ -36,7 +36,7 @@ class Otto
36
36
  #
37
37
  # @return [String] Debug representation
38
38
  def inspect
39
- "#<FailureResult reason=#{failure_reason.inspect} method=#{auth_method}>"
39
+ "#<AuthFailure reason=#{failure_reason.inspect} method=#{auth_method}>"
40
40
  end
41
41
  end
42
42
  end
@@ -1,7 +1,7 @@
1
- # frozen_string_literal: true
2
-
3
1
  # lib/otto/security/authentication/auth_strategy.rb
4
2
  #
3
+ # frozen_string_literal: true
4
+ #
5
5
  # Base class for all authentication strategies in Otto framework
6
6
  # Provides pluggable authentication patterns that can be customized per application
7
7
 
@@ -13,7 +13,9 @@ class Otto
13
13
  # Check if the request meets the authentication requirements
14
14
  # @param env [Hash] Rack environment
15
15
  # @param requirement [String] Authentication requirement string
16
- # @return [Otto::Security::Authentication::StrategyResult, nil] StrategyResult for success, nil for failure
16
+ # @return [Otto::Security::Authentication::StrategyResult,
17
+ # Otto::Security::Authentication::AuthFailure]
18
+ # StrategyResult for success, AuthFailure for failure
17
19
  def authenticate(env, requirement)
18
20
  raise NotImplementedError, 'Subclasses must implement #authenticate'
19
21
  end
@@ -21,19 +23,24 @@ class Otto
21
23
  protected
22
24
 
23
25
  # Helper to create successful strategy result
26
+ #
27
+ # NOTE: strategy_name will be injected by RouteAuthWrapper after strategy execution.
28
+ # Strategies don't know their registered name, so we pass nil here and let the wrapper
29
+ # set it based on how the strategy was registered via add_auth_strategy(name, strategy).
24
30
  def success(user:, session: {}, auth_method: nil, **metadata)
25
31
  Otto::Security::Authentication::StrategyResult.new(
26
32
  session: session,
27
33
  user: user,
28
34
  auth_method: auth_method || self.class.name.split('::').last,
29
- metadata: metadata
35
+ metadata: metadata,
36
+ strategy_name: nil # Will be set by RouteAuthWrapper
30
37
  )
31
38
  end
32
39
 
33
- # Helper for authentication failure - return FailureResult
40
+ # Helper for authentication failure - return AuthFailure
34
41
  def failure(reason = nil)
35
42
  Otto.logger.debug "[#{self.class}] Authentication failed: #{reason}" if reason
36
- Otto::Security::Authentication::FailureResult.new(
43
+ Otto::Security::Authentication::AuthFailure.new(
37
44
  failure_reason: reason || 'Authentication failed',
38
45
  auth_method: self.class.name.split('::').last
39
46
  )