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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +2 -1
- data/.github/workflows/claude-code-review.yml +1 -1
- data/.github/workflows/claude.yml +1 -1
- data/.rubocop.yml +4 -1
- data/CHANGELOG.rst +54 -6
- data/Gemfile +1 -1
- data/Gemfile.lock +19 -18
- data/docs/.gitignore +1 -0
- data/docs/migrating/v2.0.0-pre2.md +345 -0
- data/lib/otto/core/configuration.rb +2 -2
- data/lib/otto/core/middleware_stack.rb +80 -0
- data/lib/otto/core/router.rb +7 -6
- data/lib/otto/env_keys.rb +114 -0
- data/lib/otto/helpers/base.rb +2 -21
- data/lib/otto/helpers/response.rb +22 -0
- data/lib/otto/mcp/{validation.rb → schema_validation.rb} +3 -2
- data/lib/otto/mcp/server.rb +26 -13
- data/lib/otto/response_handlers/json.rb +6 -0
- data/lib/otto/route.rb +44 -48
- data/lib/otto/route_handlers/factory.rb +22 -9
- data/lib/otto/security/authentication/authentication_middleware.rb +29 -12
- data/lib/otto/security/authentication/failure_result.rb +15 -7
- data/lib/otto/security/authentication/route_auth_wrapper.rb +149 -0
- data/lib/otto/security/authentication/strategies/{public_strategy.rb → noauth_strategy.rb} +1 -1
- data/lib/otto/security/authentication/strategy_result.rb +129 -15
- data/lib/otto/security/authentication.rb +2 -2
- data/lib/otto/security/config.rb +0 -11
- data/lib/otto/security/configurator.rb +2 -2
- data/lib/otto/security/middleware/rate_limit_middleware.rb +19 -3
- data/lib/otto/version.rb +1 -1
- data/lib/otto.rb +2 -3
- data/otto.gemspec +2 -0
- metadata +26 -6
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b6215d43a8ce52d332bc046b6d51e8474c243e804bb146381fcd617fa8aebd1f
|
4
|
+
data.tar.gz: 7d73de1613bdd0f2450c2b3e16c8ef0b7020ce417f38b506c70c9b05d414d561
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4f04484aadab6fd972ecc261ed7da8291996e1623ae5df198b08550f82afa86e7d651beb7448d75b690b7505ae5d4d8443dd24c2a98bec7bb4621bcbb8b23950
|
7
|
+
data.tar.gz: bdfe655d0f597f92bfacaf89342aa0d026ecf2e89d1975ebc1835b2521a38f3bec8ba57cce94992ab0ac2eae4f01ce0080f849f82d08bb15e3473c8794fa9667
|
data/.github/workflows/ci.yml
CHANGED
@@ -41,9 +41,10 @@ jobs:
|
|
41
41
|
- uses: actions/checkout@v5
|
42
42
|
- name: Set up Ruby
|
43
43
|
uses: ruby/setup-ruby@v1
|
44
|
+
continue-on-error: ${{ matrix.experimental }}
|
44
45
|
with:
|
45
46
|
ruby-version: ${{ matrix.ruby }}
|
46
|
-
bundler-cache:
|
47
|
+
bundler-cache: ${{ !matrix.experimental }}
|
47
48
|
|
48
49
|
- name: Setup tmate session
|
49
50
|
uses: mxschmitt/action-tmate@7b6a61a73bbb9793cb80ad69b8dd8ac19261834c # v3
|
data/.rubocop.yml
CHANGED
@@ -48,7 +48,10 @@ Gemspec/DevelopmentDependencies:
|
|
48
48
|
Enabled: true
|
49
49
|
|
50
50
|
Layout/HashAlignment:
|
51
|
-
Enabled:
|
51
|
+
Enabled: true
|
52
|
+
EnforcedHashRocketStyle: key
|
53
|
+
EnforcedColonStyle: separator
|
54
|
+
EnforcedLastArgumentHashStyle: always_ignore # Changed from ignore_implicit
|
52
55
|
|
53
56
|
Lint/Void:
|
54
57
|
Enabled: false
|
data/CHANGELOG.rst
CHANGED
@@ -1,17 +1,65 @@
|
|
1
1
|
CHANGELOG.rst
|
2
2
|
=============
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
The format is based on `Keep a
|
7
|
-
Changelog <https://keepachangelog.com/en/1.1.0/>`__, and this project
|
8
|
-
adheres to `Semantic
|
9
|
-
Versioning <https://semver.org/spec/v2.0.0.html>`__.
|
4
|
+
The format is based on `Keep a Changelog <https://keepachangelog.com/en/1.1.0/>`__, and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0.html>`__.
|
10
5
|
|
11
6
|
.. raw:: html
|
12
7
|
|
13
8
|
<!--scriv-insert-here-->
|
14
9
|
|
10
|
+
.. _changelog-2.0.0.pre2:
|
11
|
+
|
12
|
+
2.0.0.pre2 — 2025-10-11
|
13
|
+
=======================
|
14
|
+
|
15
|
+
Added
|
16
|
+
-----
|
17
|
+
|
18
|
+
- Added `StrategyResult` class with improved user model compatibility and cleaner API
|
19
|
+
- Helper methods ``authenticated?``, ``has_role?``, ``has_permission?``, ``user_name``, ``session_id`` for cleaner Logic class implementation
|
20
|
+
- Added JSON request body parsing support in Logic class handlers
|
21
|
+
- Added new modular directory structure under ``lib/otto/security/``
|
22
|
+
- Added backward compatibility aliases to maintain existing API compatibility
|
23
|
+
- Added proper namespacing for authentication components and middleware classes
|
24
|
+
|
25
|
+
Changed
|
26
|
+
-------
|
27
|
+
|
28
|
+
- **BREAKING**: Logic class constructor signature changed from ``initialize(session, user, params, locale)`` to ``initialize(context, params, locale)``
|
29
|
+
- Logic classes now receive an immutable context object instead of separate session/user parameters
|
30
|
+
- LogicClassHandler simplified to single arity pattern, removing backward compatibility code
|
31
|
+
- Authentication middleware now creates `StrategyResult` instances for all requests
|
32
|
+
- Replaced `RequestContext` with `StrategyResult` class for better authentication handling
|
33
|
+
- Simplified authentication strategy API to return `StrategyResult` or `nil` for success/failure
|
34
|
+
- Enhanced route handlers to support JSON request body parsing
|
35
|
+
- Updated authentication middleware to use `StrategyResult` throughout
|
36
|
+
- Reorganized Otto security module structure for better maintainability and separation of concerns
|
37
|
+
- Moved authentication strategies to ``Otto::Security::Authentication::Strategies`` namespace
|
38
|
+
- Moved security middleware to ``Otto::Security::Middleware`` namespace
|
39
|
+
- Moved ``StrategyResult`` and ``FailureResult`` to ``Otto::Security::Authentication`` namespace
|
40
|
+
|
41
|
+
Removed
|
42
|
+
-------
|
43
|
+
|
44
|
+
- Removed `RequestContext` class (which was introduced and then replaced by `StrategyResult` during this development cycle)
|
45
|
+
- Removed `AuthResult` class from authentication system
|
46
|
+
- Removed `ConcurrentCacheStore` example class for an ActiveSupport::Cache::MemoryStore-compatible interface with Rack::Attack
|
47
|
+
- Removed OpenStruct dependency across the framework
|
48
|
+
|
49
|
+
Documentation
|
50
|
+
-------------
|
51
|
+
|
52
|
+
- Updated migration guide with comprehensive examples for the new context object and step-by-step conversion instructions
|
53
|
+
- Updated Logic class examples in advanced_routes and authentication_strategies to demonstrate new pattern
|
54
|
+
- Enhanced documentation with API reference and helper method examples for the new context object
|
55
|
+
|
56
|
+
AI Assistance
|
57
|
+
-------------
|
58
|
+
|
59
|
+
- AI-assisted architectural design for RequestContext Data class and security module reorganization
|
60
|
+
- Comprehensive migration of Logic classes and documentation with AI guidance for consistency
|
61
|
+
- Automated test validation and intelligent file organization following Ruby conventions
|
62
|
+
|
15
63
|
.. _changelog-2.0.0-pre1:
|
16
64
|
|
17
65
|
2.0.0-pre1 — 2025-09-10
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
otto (2.0.0.
|
4
|
+
otto (2.0.0.pre2)
|
5
5
|
facets (~> 3.1)
|
6
|
+
logger (~> 1, < 2.0)
|
6
7
|
loofah (~> 2.20)
|
7
8
|
rack (~> 3.1, < 4.0)
|
8
9
|
rack-parser (~> 0.7)
|
@@ -28,7 +29,7 @@ GEM
|
|
28
29
|
pp (>= 0.6.0)
|
29
30
|
rdoc (>= 4.0.0)
|
30
31
|
reline (>= 0.4.2)
|
31
|
-
json (2.
|
32
|
+
json (2.15.1)
|
32
33
|
json_schemer (2.4.0)
|
33
34
|
bigdecimal
|
34
35
|
hana (~> 1.3)
|
@@ -41,21 +42,21 @@ GEM
|
|
41
42
|
crass (~> 1.0.2)
|
42
43
|
nokogiri (>= 1.12.0)
|
43
44
|
minitest (5.25.5)
|
44
|
-
nokogiri (1.18.
|
45
|
+
nokogiri (1.18.10-aarch64-linux-gnu)
|
45
46
|
racc (~> 1.4)
|
46
|
-
nokogiri (1.18.
|
47
|
+
nokogiri (1.18.10-aarch64-linux-musl)
|
47
48
|
racc (~> 1.4)
|
48
|
-
nokogiri (1.18.
|
49
|
+
nokogiri (1.18.10-arm-linux-gnu)
|
49
50
|
racc (~> 1.4)
|
50
|
-
nokogiri (1.18.
|
51
|
+
nokogiri (1.18.10-arm-linux-musl)
|
51
52
|
racc (~> 1.4)
|
52
|
-
nokogiri (1.18.
|
53
|
+
nokogiri (1.18.10-arm64-darwin)
|
53
54
|
racc (~> 1.4)
|
54
|
-
nokogiri (1.18.
|
55
|
+
nokogiri (1.18.10-x86_64-darwin)
|
55
56
|
racc (~> 1.4)
|
56
|
-
nokogiri (1.18.
|
57
|
+
nokogiri (1.18.10-x86_64-linux-gnu)
|
57
58
|
racc (~> 1.4)
|
58
|
-
nokogiri (1.18.
|
59
|
+
nokogiri (1.18.10-x86_64-linux-musl)
|
59
60
|
racc (~> 1.4)
|
60
61
|
parallel (1.27.0)
|
61
62
|
parser (3.3.9.0)
|
@@ -67,12 +68,12 @@ GEM
|
|
67
68
|
prettyprint
|
68
69
|
prettier_print (1.2.1)
|
69
70
|
prettyprint (0.2.0)
|
70
|
-
prism (1.
|
71
|
+
prism (1.5.2)
|
71
72
|
psych (5.2.6)
|
72
73
|
date
|
73
74
|
stringio
|
74
75
|
racc (1.8.1)
|
75
|
-
rack (3.2.
|
76
|
+
rack (3.2.2)
|
76
77
|
rack-attack (6.7.0)
|
77
78
|
rack (>= 1.0, < 4)
|
78
79
|
rack-parser (0.7.0)
|
@@ -87,10 +88,10 @@ GEM
|
|
87
88
|
rdoc (6.14.2)
|
88
89
|
erb
|
89
90
|
psych (>= 4.0.0)
|
90
|
-
regexp_parser (2.11.
|
91
|
+
regexp_parser (2.11.3)
|
91
92
|
reline (0.6.2)
|
92
93
|
io-console (~> 0.5)
|
93
|
-
rexml (3.4.
|
94
|
+
rexml (3.4.4)
|
94
95
|
rspec (3.13.1)
|
95
96
|
rspec-core (~> 3.13.0)
|
96
97
|
rspec-expectations (~> 3.13.0)
|
@@ -104,7 +105,7 @@ GEM
|
|
104
105
|
diff-lcs (>= 1.2.0, < 2.0)
|
105
106
|
rspec-support (~> 3.13.0)
|
106
107
|
rspec-support (3.13.5)
|
107
|
-
rubocop (1.
|
108
|
+
rubocop (1.81.1)
|
108
109
|
json (~> 2.3)
|
109
110
|
language_server-protocol (~> 3.17.0.2)
|
110
111
|
lint_roller (~> 1.1.0)
|
@@ -112,10 +113,10 @@ GEM
|
|
112
113
|
parser (>= 3.3.0.2)
|
113
114
|
rainbow (>= 2.2.2, < 4.0)
|
114
115
|
regexp_parser (>= 2.9.3, < 3.0)
|
115
|
-
rubocop-ast (>= 1.
|
116
|
+
rubocop-ast (>= 1.47.1, < 2.0)
|
116
117
|
ruby-progressbar (~> 1.7)
|
117
118
|
unicode-display_width (>= 2.4.0, < 4.0)
|
118
|
-
rubocop-ast (1.
|
119
|
+
rubocop-ast (1.47.1)
|
119
120
|
parser (>= 3.3.7.2)
|
120
121
|
prism (~> 1.4)
|
121
122
|
rubocop-performance (1.26.0)
|
@@ -173,7 +174,7 @@ DEPENDENCIES
|
|
173
174
|
rack-test
|
174
175
|
rackup
|
175
176
|
rspec (~> 3.13)
|
176
|
-
rubocop
|
177
|
+
rubocop (~> 1.81.1)
|
177
178
|
rubocop-performance
|
178
179
|
rubocop-rspec
|
179
180
|
rubocop-thread_safety
|
data/docs/.gitignore
CHANGED
@@ -0,0 +1,345 @@
|
|
1
|
+
# Otto v2.0.0-pre2 Migration Guide
|
2
|
+
|
3
|
+
## Overview
|
4
|
+
|
5
|
+
This release resolves critical architectural issues with `StrategyResult` semantics and removes deprecated methods. The changes clarify the distinction between "request state" (user in session) and "authentication outcomes" (auth attempt just succeeded).
|
6
|
+
|
7
|
+
## Breaking Changes
|
8
|
+
|
9
|
+
### 1. StrategyResult Methods Removed
|
10
|
+
|
11
|
+
**Removed Methods:**
|
12
|
+
- `StrategyResult#success?` - Always returned `true`, meaningless
|
13
|
+
- `StrategyResult#failure?` - Always returned `false`, meaningless
|
14
|
+
- `FailureResult#success?` - Always returned `false`
|
15
|
+
- `FailureResult#failure?` - Always returned `true`
|
16
|
+
|
17
|
+
**Migration:**
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
# Before - checking success/failure
|
21
|
+
if strategy_result&.success?
|
22
|
+
# handle success
|
23
|
+
end
|
24
|
+
|
25
|
+
# After - type checking
|
26
|
+
if strategy_result.is_a?(Otto::Security::Authentication::StrategyResult)
|
27
|
+
# handle success
|
28
|
+
end
|
29
|
+
|
30
|
+
# Or for middleware - FailureResult indicates failure
|
31
|
+
if strategy_result.is_a?(Otto::Security::Authentication::FailureResult)
|
32
|
+
# handle failure
|
33
|
+
else
|
34
|
+
# handle success
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
### 2. New Semantic Distinction
|
39
|
+
|
40
|
+
**New Method:**
|
41
|
+
- `StrategyResult#auth_attempt_succeeded?` - Returns `true` only when auth strategy just executed successfully
|
42
|
+
|
43
|
+
**Key Semantic Difference:**
|
44
|
+
|
45
|
+
| Method | Meaning | Use Case |
|
46
|
+
|--------|---------|----------|
|
47
|
+
| `authenticated?` | User in session (request state) | Check if session has user |
|
48
|
+
| `auth_attempt_succeeded?` | Auth strategy just succeeded (auth outcome) | Post-login redirects, analytics |
|
49
|
+
|
50
|
+
**Migration Examples:**
|
51
|
+
|
52
|
+
#### Registration Flow (IMPORTANT)
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
# Before - BROKEN - blocks legitimate registration
|
56
|
+
class CreateAccount < Logic::Base
|
57
|
+
def raise_concerns
|
58
|
+
# This was always true if user in session, blocking registration
|
59
|
+
raise OT::FormError, "Already signed up" if @strategy_result.success?
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# After - CORRECT
|
64
|
+
class CreateAccount < Logic::Base
|
65
|
+
def raise_concerns
|
66
|
+
# Check if user already in session
|
67
|
+
raise OT::FormError, "Already signed up" if @strategy_result.authenticated?
|
68
|
+
end
|
69
|
+
end
|
70
|
+
```
|
71
|
+
|
72
|
+
#### Post-Login Redirect
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
# Before - unreliable
|
76
|
+
class AuthController
|
77
|
+
def authenticate
|
78
|
+
if @strategy_result.success? # Always true, not helpful
|
79
|
+
redirect_to dashboard_path
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# After - correct semantic
|
85
|
+
class AuthController
|
86
|
+
def authenticate
|
87
|
+
if @strategy_result.auth_attempt_succeeded?
|
88
|
+
# Only redirect when auth route just succeeded
|
89
|
+
redirect_to dashboard_path
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
## Non-Breaking Enhancements
|
96
|
+
|
97
|
+
### 1. Comprehensive Documentation
|
98
|
+
|
99
|
+
`StrategyResult` now includes extensive inline documentation:
|
100
|
+
- Usage patterns and creation guidelines
|
101
|
+
- Session contract for multi-app architectures
|
102
|
+
- Examples for common scenarios
|
103
|
+
- Clear distinction between request state and auth outcomes
|
104
|
+
|
105
|
+
### 2. Session Contract (Multi-App Architectures)
|
106
|
+
|
107
|
+
For shared session architectures (Auth app + Core app):
|
108
|
+
|
109
|
+
**Required session keys for authenticated state:**
|
110
|
+
```ruby
|
111
|
+
session['authenticated'] # Boolean flag
|
112
|
+
session['identity_id'] # User/customer ID
|
113
|
+
session['authenticated_at'] # Timestamp
|
114
|
+
```
|
115
|
+
|
116
|
+
**Optional session keys:**
|
117
|
+
```ruby
|
118
|
+
session['email'] # User email
|
119
|
+
session['ip_address'] # Client IP
|
120
|
+
session['user_agent'] # Client UA
|
121
|
+
session['locale'] # User locale
|
122
|
+
```
|
123
|
+
|
124
|
+
**Advanced mode adds:**
|
125
|
+
```ruby
|
126
|
+
session['account_external_id'] # Rodauth external_id
|
127
|
+
session['advanced_account_id'] # Rodauth account ID
|
128
|
+
```
|
129
|
+
|
130
|
+
## Application Code Updates Required
|
131
|
+
|
132
|
+
### 1. Remove Manual StrategyResult Creation
|
133
|
+
|
134
|
+
**Anti-pattern identified:**
|
135
|
+
```ruby
|
136
|
+
# BAD - Bypasses Otto's auth_method tracking
|
137
|
+
class Controller::Base
|
138
|
+
def _strategy_result
|
139
|
+
Otto::Security::Authentication::StrategyResult.new(
|
140
|
+
session: session,
|
141
|
+
user: cust,
|
142
|
+
auth_method: 'session', # Hardcoded - loses semantic meaning
|
143
|
+
metadata: { ip: req.client_ipaddress }
|
144
|
+
)
|
145
|
+
end
|
146
|
+
end
|
147
|
+
```
|
148
|
+
|
149
|
+
**Correct approach:**
|
150
|
+
```ruby
|
151
|
+
# GOOD - Use middleware-provided result
|
152
|
+
class Controller::Base
|
153
|
+
def strategy_result
|
154
|
+
req.env['otto.strategy_result'] # Created by AuthenticationMiddleware
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Or for non-auth checks, use session directly
|
159
|
+
class Controller::Base
|
160
|
+
def current_user
|
161
|
+
return nil unless session['authenticated']
|
162
|
+
Customer.find(session['identity_id'])
|
163
|
+
end
|
164
|
+
end
|
165
|
+
```
|
166
|
+
|
167
|
+
### 2. Update Logic Classes
|
168
|
+
|
169
|
+
**Pattern to check for:**
|
170
|
+
```ruby
|
171
|
+
# Search your codebase for these patterns:
|
172
|
+
grep -r "@strategy_result.success?" apps/
|
173
|
+
grep -r "@context.success?" apps/
|
174
|
+
grep -r "strategy_result&.success?" apps/
|
175
|
+
```
|
176
|
+
|
177
|
+
**Update to:**
|
178
|
+
- Use `authenticated?` for "user in session" checks (registration, profile access, etc.)
|
179
|
+
- Use `auth_attempt_succeeded?` for "just logged in" checks (redirects, welcome messages, etc.)
|
180
|
+
|
181
|
+
### 3. Test Updates
|
182
|
+
|
183
|
+
**RSpec matchers:**
|
184
|
+
```ruby
|
185
|
+
# Before
|
186
|
+
expect(result).to be_success
|
187
|
+
expect(result).to be_failure
|
188
|
+
|
189
|
+
# After
|
190
|
+
expect(result).to be_a(Otto::Security::Authentication::StrategyResult)
|
191
|
+
expect(result).to be_a(Otto::Security::Authentication::FailureResult)
|
192
|
+
```
|
193
|
+
|
194
|
+
## Architecture Clarifications
|
195
|
+
|
196
|
+
### When StrategyResult is Created
|
197
|
+
|
198
|
+
1. **Routes WITH `auth=...` requirement:**
|
199
|
+
- Strategy executes
|
200
|
+
- Returns `StrategyResult` (success) or `FailureResult` (failure)
|
201
|
+
- Middleware converts `FailureResult` to anonymous `StrategyResult` + 401 response
|
202
|
+
|
203
|
+
2. **Routes WITHOUT `auth=...` requirement:**
|
204
|
+
- Middleware creates anonymous `StrategyResult`
|
205
|
+
- Sets `auth_method: 'anonymous'`
|
206
|
+
|
207
|
+
3. **Auth app (Roda) routes:**
|
208
|
+
- Manually creates `StrategyResult` for Logic class compatibility
|
209
|
+
- Same interface as Otto controllers
|
210
|
+
|
211
|
+
### Integration Boundaries
|
212
|
+
|
213
|
+
**Multi-app setup (Auth + Core + API):**
|
214
|
+
- **Shared:** Session middleware, Redis session, Logic classes, Customer model
|
215
|
+
- **Auth app:** Creates StrategyResult manually, uses Roda routing
|
216
|
+
- **Core/API apps:** StrategyResult from AuthenticationMiddleware
|
217
|
+
- **Integration:** Pure session-based, no direct code calls between apps
|
218
|
+
|
219
|
+
## Testing Your Migration
|
220
|
+
|
221
|
+
### 1. Registration Flow Test
|
222
|
+
|
223
|
+
```ruby
|
224
|
+
describe "CreateAccount" do
|
225
|
+
it "blocks registration when user already authenticated" do
|
226
|
+
strategy_result = Otto::Security::Authentication::StrategyResult.new(
|
227
|
+
session: { user_id: 123 },
|
228
|
+
user: { id: 123 },
|
229
|
+
auth_method: 'anonymous', # No auth route, but user in session
|
230
|
+
metadata: {}
|
231
|
+
)
|
232
|
+
|
233
|
+
logic = CreateAccount.new(strategy_result, params, 'en')
|
234
|
+
|
235
|
+
expect { logic.raise_concerns }.to raise_error(OT::FormError, /Already signed up/)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
```
|
239
|
+
|
240
|
+
### 2. Auth Attempt Test
|
241
|
+
|
242
|
+
```ruby
|
243
|
+
describe "LoginHandler" do
|
244
|
+
it "redirects after successful authentication" do
|
245
|
+
strategy_result = Otto::Security::Authentication::StrategyResult.new(
|
246
|
+
session: { user_id: 123 },
|
247
|
+
user: { id: 123 },
|
248
|
+
auth_method: 'session', # Auth route succeeded
|
249
|
+
metadata: {}
|
250
|
+
)
|
251
|
+
|
252
|
+
expect(strategy_result.authenticated?).to be true
|
253
|
+
expect(strategy_result.auth_attempt_succeeded?).to be true
|
254
|
+
end
|
255
|
+
end
|
256
|
+
```
|
257
|
+
|
258
|
+
## Checklist
|
259
|
+
|
260
|
+
- [ ] Remove all usage of `success?` and `failure?` methods
|
261
|
+
- [ ] Update registration flows to use `authenticated?`
|
262
|
+
- [ ] Update post-login flows to use `auth_attempt_succeeded?` if needed
|
263
|
+
- [ ] Remove manual `StrategyResult` creation in controllers
|
264
|
+
- [ ] Update test matchers from `be_success`/`be_failure` to type checks
|
265
|
+
- [ ] Verify session contract keys match across apps
|
266
|
+
- [ ] Run full test suite: `bundle exec rspec`
|
267
|
+
- [ ] Test registration while logged in (should be blocked)
|
268
|
+
- [ ] Test login redirect flow (should work correctly)
|
269
|
+
|
270
|
+
## Configuration Updates
|
271
|
+
|
272
|
+
### Authentication Login Path Configuration
|
273
|
+
|
274
|
+
When authentication fails for HTML requests, Otto redirects to a login page. You can configure this path:
|
275
|
+
|
276
|
+
```ruby
|
277
|
+
# Initialize with login_path configuration
|
278
|
+
otto = Otto.new do
|
279
|
+
auth_config[:login_path] = '/auth/login' # Default: '/signin'
|
280
|
+
end
|
281
|
+
|
282
|
+
# Or configure after initialization
|
283
|
+
otto.auth_config[:login_path] = '/custom/login'
|
284
|
+
```
|
285
|
+
|
286
|
+
**Note:** If not configured, the default fallback is `/signin`. Ensure this route exists or configure your actual login path to avoid 404 errors on authentication failures.
|
287
|
+
|
288
|
+
## Additional Improvements in v2.0.0-pre2
|
289
|
+
|
290
|
+
### Middleware Architecture Enhancements
|
291
|
+
|
292
|
+
**1. Renamed MCP ValidationMiddleware → SchemaValidationMiddleware**
|
293
|
+
- Resolves naming collision with `Otto::Security::ValidationMiddleware`
|
294
|
+
- `Otto::MCP::SchemaValidationMiddleware` now clearly indicates JSON schema validation
|
295
|
+
- `Otto::Security::ValidationMiddleware` remains for input sanitization
|
296
|
+
|
297
|
+
**Migration:**
|
298
|
+
```ruby
|
299
|
+
# File renamed: lib/otto/mcp/validation.rb → lib/otto/mcp/schema_validation.rb
|
300
|
+
# Class renamed automatically if using Otto's MCP server
|
301
|
+
# No action needed for most users
|
302
|
+
```
|
303
|
+
|
304
|
+
**2. Centralized Env Keys Documentation**
|
305
|
+
- New file: `lib/otto/env_keys.rb`
|
306
|
+
- Documents all `env['otto.*']` keys with types, setters, and users
|
307
|
+
- Includes usage examples and multi-app integration patterns
|
308
|
+
- Essential reference for custom middleware development
|
309
|
+
|
310
|
+
**3. RateLimitMiddleware Clarity**
|
311
|
+
- Added documentation clarifying it's a CONFIGURATOR, not enforcer
|
312
|
+
- Actual rate limiting happens in Rack::Attack middleware
|
313
|
+
- `call` method is explicitly a pass-through
|
314
|
+
|
315
|
+
**4. Middleware Order Enforcement**
|
316
|
+
- New method: `MiddlewareStack#validate_mcp_middleware_order`
|
317
|
+
- New method: `MiddlewareStack#add_with_position` for explicit ordering
|
318
|
+
- MCP Server uses explicit positioning: `position: :first` and `position: :last`
|
319
|
+
- Validates middleware order and warns if suboptimal
|
320
|
+
- Optimal: RateLimitMiddleware → TokenMiddleware → SchemaValidationMiddleware
|
321
|
+
- Validation runs automatically when MCP is enabled
|
322
|
+
|
323
|
+
**Usage Example:**
|
324
|
+
```ruby
|
325
|
+
# Explicit positioning for clarity
|
326
|
+
middleware.add_with_position(
|
327
|
+
Otto::MCP::RateLimitMiddleware,
|
328
|
+
security_config,
|
329
|
+
position: :first # Ensures rate limiting runs first
|
330
|
+
)
|
331
|
+
|
332
|
+
middleware.add_with_position(
|
333
|
+
Otto::MCP::SchemaValidationMiddleware,
|
334
|
+
position: :last # Ensures validation runs last
|
335
|
+
)
|
336
|
+
```
|
337
|
+
|
338
|
+
## Questions?
|
339
|
+
|
340
|
+
Review the comprehensive inline documentation in:
|
341
|
+
- `lib/otto/security/authentication/strategy_result.rb` (lines 1-90) - Auth semantics
|
342
|
+
- `lib/otto/security/authentication/authentication_middleware.rb` - Auth middleware
|
343
|
+
- `lib/otto/env_keys.rb` - Complete env key registry
|
344
|
+
|
345
|
+
The documentation includes detailed usage patterns, session contracts, and examples for common scenarios.
|
@@ -143,12 +143,12 @@ class Otto
|
|
143
143
|
# @param default_strategy [String] Default strategy to use when none specified
|
144
144
|
# @example
|
145
145
|
# otto.configure_auth_strategies({
|
146
|
-
# '
|
146
|
+
# 'noauth' => Otto::Security::Authentication::Strategies::NoAuthStrategy.new,
|
147
147
|
# 'authenticated' => Otto::Security::Authentication::Strategies::SessionStrategy.new(session_key: 'user_id'),
|
148
148
|
# 'role:admin' => Otto::Security::Authentication::Strategies::RoleStrategy.new(['admin']),
|
149
149
|
# 'api_key' => Otto::Security::Authentication::Strategies::APIKeyStrategy.new(api_keys: ['secret123'])
|
150
150
|
# })
|
151
|
-
def configure_auth_strategies(strategies, default_strategy: '
|
151
|
+
def configure_auth_strategies(strategies, default_strategy: 'noauth')
|
152
152
|
# Update existing @auth_config rather than creating a new one
|
153
153
|
@auth_config[:auth_strategies] = strategies
|
154
154
|
@auth_config[:default_auth_strategy] = default_strategy
|