otto 2.0.0.pre1 → 2.0.0.pre3
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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +2 -3
- data/.github/workflows/claude-code-review.yml +30 -14
- data/.github/workflows/claude.yml +1 -1
- data/.rubocop.yml +4 -1
- data/CHANGELOG.rst +54 -6
- data/CLAUDE.md +537 -0
- data/Gemfile +3 -2
- data/Gemfile.lock +34 -26
- data/benchmark_middleware_wrap.rb +163 -0
- data/changelog.d/20251014_144317_delano_54_thats_a_wrapper.rst +36 -0
- data/changelog.d/20251014_161526_delano_54_thats_a_wrapper.rst +5 -0
- data/docs/.gitignore +2 -0
- data/docs/ipaddr-encoding-quirk.md +34 -0
- data/docs/migrating/v2.0.0-pre2.md +338 -0
- data/examples/authentication_strategies/config.ru +0 -1
- data/lib/otto/core/configuration.rb +91 -41
- data/lib/otto/core/freezable.rb +93 -0
- data/lib/otto/core/middleware_stack.rb +103 -16
- data/lib/otto/core/router.rb +8 -7
- data/lib/otto/core.rb +8 -0
- data/lib/otto/env_keys.rb +118 -0
- data/lib/otto/helpers/base.rb +2 -21
- data/lib/otto/helpers/request.rb +80 -2
- data/lib/otto/helpers/response.rb +25 -3
- data/lib/otto/helpers.rb +4 -0
- data/lib/otto/locale/config.rb +56 -0
- data/lib/otto/mcp/{validation.rb → schema_validation.rb} +3 -2
- data/lib/otto/mcp/server.rb +26 -13
- data/lib/otto/mcp.rb +3 -0
- data/lib/otto/privacy/config.rb +199 -0
- data/lib/otto/privacy/geo_resolver.rb +115 -0
- data/lib/otto/privacy/ip_privacy.rb +175 -0
- data/lib/otto/privacy/redacted_fingerprint.rb +136 -0
- data/lib/otto/privacy.rb +29 -0
- data/lib/otto/response_handlers/json.rb +6 -0
- data/lib/otto/route.rb +44 -48
- data/lib/otto/route_handlers/base.rb +1 -2
- data/lib/otto/route_handlers/factory.rb +24 -9
- data/lib/otto/route_handlers/logic_class.rb +2 -2
- data/lib/otto/security/authentication/auth_failure.rb +44 -0
- data/lib/otto/security/authentication/auth_strategy.rb +3 -3
- data/lib/otto/security/authentication/route_auth_wrapper.rb +260 -0
- data/lib/otto/security/authentication/strategies/{public_strategy.rb → noauth_strategy.rb} +6 -2
- data/lib/otto/security/authentication/strategy_result.rb +129 -15
- data/lib/otto/security/authentication.rb +5 -6
- data/lib/otto/security/config.rb +51 -18
- data/lib/otto/security/configurator.rb +2 -15
- data/lib/otto/security/middleware/ip_privacy_middleware.rb +211 -0
- data/lib/otto/security/middleware/rate_limit_middleware.rb +19 -3
- data/lib/otto/security.rb +9 -0
- data/lib/otto/version.rb +1 -1
- data/lib/otto.rb +183 -89
- data/otto.gemspec +5 -0
- metadata +83 -8
- data/changelog.d/20250911_235619_delano_next.rst +0 -28
- data/changelog.d/20250912_123055_delano_remove_ostruct.rst +0 -21
- data/changelog.d/20250912_175625_claude_delano_remove_ostruct.rst +0 -21
- data/lib/otto/security/authentication/authentication_middleware.rb +0 -123
- data/lib/otto/security/authentication/failure_result.rb +0 -36
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative 'strategy_result'
|
|
4
|
-
require_relative 'failure_result'
|
|
5
|
-
require_relative 'strategies/public_strategy'
|
|
6
|
-
require_relative 'strategies/role_strategy'
|
|
7
|
-
require_relative 'strategies/permission_strategy'
|
|
8
|
-
|
|
9
|
-
class Otto
|
|
10
|
-
module Security
|
|
11
|
-
module Authentication
|
|
12
|
-
# Authentication middleware that enforces route-level auth requirements
|
|
13
|
-
class AuthenticationMiddleware
|
|
14
|
-
def initialize(app, security_config = {}, config = {})
|
|
15
|
-
@app = app
|
|
16
|
-
@security_config = security_config
|
|
17
|
-
@config = config
|
|
18
|
-
@strategies = config[:auth_strategies] || {}
|
|
19
|
-
@default_strategy = config[:default_auth_strategy] || 'publicly'
|
|
20
|
-
|
|
21
|
-
# Add default public strategy if not provided
|
|
22
|
-
@strategies['publicly'] ||= Strategies::PublicStrategy.new
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def call(env)
|
|
26
|
-
# Check if this route has auth requirements
|
|
27
|
-
route_definition = env['otto.route_definition']
|
|
28
|
-
|
|
29
|
-
# If no route definition, create anonymous result and continue
|
|
30
|
-
unless route_definition
|
|
31
|
-
env['otto.strategy_result'] = Otto::Security::Authentication::StrategyResult.anonymous(
|
|
32
|
-
metadata: { ip: env['REMOTE_ADDR'] }
|
|
33
|
-
)
|
|
34
|
-
return @app.call(env)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
auth_requirement = route_definition.auth_requirement
|
|
38
|
-
|
|
39
|
-
# If no auth requirement, create anonymous result and continue
|
|
40
|
-
unless auth_requirement
|
|
41
|
-
env['otto.strategy_result'] = Otto::Security::Authentication::StrategyResult.anonymous(
|
|
42
|
-
metadata: { ip: env['REMOTE_ADDR'] }
|
|
43
|
-
)
|
|
44
|
-
return @app.call(env)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Find appropriate strategy
|
|
48
|
-
strategy = find_strategy(auth_requirement)
|
|
49
|
-
return auth_error_response("Unknown authentication strategy: #{auth_requirement}") unless strategy
|
|
50
|
-
|
|
51
|
-
# Perform authentication
|
|
52
|
-
strategy_result = strategy.authenticate(env, auth_requirement)
|
|
53
|
-
|
|
54
|
-
if strategy_result&.success?
|
|
55
|
-
# Success - store the strategy result directly
|
|
56
|
-
env['otto.strategy_result'] = strategy_result
|
|
57
|
-
env['otto.user'] = strategy_result.user # For convenience
|
|
58
|
-
env['otto.user_context'] = strategy_result.user_context # For convenience
|
|
59
|
-
@app.call(env)
|
|
60
|
-
else
|
|
61
|
-
# Failure - create anonymous result with failure info
|
|
62
|
-
failure_reason = strategy_result&.failure_reason || 'Authentication failed'
|
|
63
|
-
env['otto.strategy_result'] = Otto::Security::Authentication::StrategyResult.anonymous(
|
|
64
|
-
metadata: {
|
|
65
|
-
ip: env['REMOTE_ADDR'],
|
|
66
|
-
auth_failure: failure_reason,
|
|
67
|
-
attempted_strategy: auth_requirement,
|
|
68
|
-
}
|
|
69
|
-
)
|
|
70
|
-
auth_error_response(failure_reason)
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
private
|
|
75
|
-
|
|
76
|
-
def find_strategy(requirement)
|
|
77
|
-
# Try exact match first - this has highest priority
|
|
78
|
-
return @strategies[requirement] if @strategies[requirement]
|
|
79
|
-
|
|
80
|
-
# For colon-separated requirements like "role:admin", try prefix match
|
|
81
|
-
if requirement.include?(':')
|
|
82
|
-
prefix = requirement.split(':', 2).first
|
|
83
|
-
|
|
84
|
-
# Check if we have a strategy registered for the prefix
|
|
85
|
-
prefix_strategy = @strategies[prefix]
|
|
86
|
-
return prefix_strategy if prefix_strategy
|
|
87
|
-
|
|
88
|
-
# Try fallback patterns for role: and permission: requirements
|
|
89
|
-
if requirement.start_with?('role:')
|
|
90
|
-
return @strategies['role'] || Strategies::RoleStrategy.new([])
|
|
91
|
-
elsif requirement.start_with?('permission:')
|
|
92
|
-
return @strategies['permission'] || Strategies::PermissionStrategy.new([])
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
nil
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def auth_error_response(message)
|
|
100
|
-
body = JSON.generate({
|
|
101
|
-
error: 'Authentication Required',
|
|
102
|
-
message: message,
|
|
103
|
-
timestamp: Time.now.to_i,
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
headers = {
|
|
107
|
-
'Content-Type' => 'application/json',
|
|
108
|
-
'Content-Length' => body.bytesize.to_s,
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
# Add security headers if available from config hash or Otto instance
|
|
112
|
-
if @config.is_a?(Hash) && @config[:security_headers]
|
|
113
|
-
headers.merge!(@config[:security_headers])
|
|
114
|
-
elsif @config.respond_to?(:security_config) && @config.security_config
|
|
115
|
-
headers.merge!(@config.security_config.security_headers)
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
[401, headers, [body]]
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
end
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# lib/otto/security/authentication/failure_result.rb
|
|
4
|
-
|
|
5
|
-
class Otto
|
|
6
|
-
module Security
|
|
7
|
-
module Authentication
|
|
8
|
-
# Failure result for authentication failures
|
|
9
|
-
FailureResult = Data.define(:failure_reason, :auth_method) do
|
|
10
|
-
def success?
|
|
11
|
-
false
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def failure?
|
|
15
|
-
true
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def authenticated?
|
|
19
|
-
false
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def anonymous?
|
|
23
|
-
true
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def user_context
|
|
27
|
-
{}
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def inspect
|
|
31
|
-
"#<FailureResult reason=#{failure_reason.inspect} method=#{auth_method}>"
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|