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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +1 -3
- data/.github/workflows/claude-code-review.yml +29 -13
- data/.github/workflows/code-smells.yml +146 -0
- data/.gitignore +4 -0
- data/.pre-commit-config.yaml +2 -2
- data/.reek.yml +99 -0
- data/CHANGELOG.rst +90 -0
- data/CLAUDE.md +116 -45
- data/Gemfile +5 -2
- data/Gemfile.lock +70 -24
- data/README.md +49 -1
- data/changelog.d/20251103_235431_delano_86_improve_error_logging.rst +15 -0
- data/changelog.d/20251109_025012_claude_fix_backtrace_sanitization.rst +37 -0
- data/docs/.gitignore +1 -0
- data/docs/ipaddr-encoding-quirk.md +34 -0
- data/docs/migrating/v2.0.0-pre2.md +11 -18
- data/examples/advanced_routes/README.md +137 -20
- data/examples/authentication_strategies/README.md +212 -19
- data/examples/authentication_strategies/config.ru +0 -1
- data/examples/backtrace_sanitization_demo.rb +86 -0
- data/examples/basic/README.md +61 -10
- data/examples/error_handler_registration.rb +136 -0
- data/examples/logging_improvements.rb +76 -0
- data/examples/mcp_demo/README.md +187 -27
- data/examples/security_features/README.md +249 -30
- data/examples/simple_geo_resolver.rb +107 -0
- data/lib/otto/core/configuration.rb +90 -45
- data/lib/otto/core/error_handler.rb +138 -8
- data/lib/otto/core/file_safety.rb +2 -2
- data/lib/otto/core/freezable.rb +93 -0
- data/lib/otto/core/middleware_stack.rb +25 -18
- data/lib/otto/core/router.rb +62 -9
- data/lib/otto/core/uri_generator.rb +2 -2
- data/lib/otto/core.rb +10 -0
- data/lib/otto/design_system.rb +2 -2
- data/lib/otto/env_keys.rb +65 -12
- data/lib/otto/helpers/base.rb +2 -2
- data/lib/otto/helpers/request.rb +85 -2
- data/lib/otto/helpers/response.rb +5 -5
- data/lib/otto/helpers/validation.rb +2 -2
- data/lib/otto/helpers.rb +6 -0
- data/lib/otto/locale/config.rb +56 -0
- data/lib/otto/locale/middleware.rb +160 -0
- data/lib/otto/locale.rb +10 -0
- data/lib/otto/logging_helpers.rb +273 -0
- data/lib/otto/mcp/auth/token.rb +2 -2
- data/lib/otto/mcp/protocol.rb +2 -2
- data/lib/otto/mcp/rate_limiting.rb +2 -2
- data/lib/otto/mcp/registry.rb +2 -2
- data/lib/otto/mcp/route_parser.rb +2 -2
- data/lib/otto/mcp/schema_validation.rb +2 -2
- data/lib/otto/mcp/server.rb +2 -2
- data/lib/otto/mcp.rb +5 -0
- data/lib/otto/privacy/config.rb +201 -0
- data/lib/otto/privacy/geo_resolver.rb +285 -0
- data/lib/otto/privacy/ip_privacy.rb +177 -0
- data/lib/otto/privacy/redacted_fingerprint.rb +146 -0
- data/lib/otto/privacy.rb +31 -0
- data/lib/otto/response_handlers/auto.rb +2 -0
- data/lib/otto/response_handlers/base.rb +2 -0
- data/lib/otto/response_handlers/default.rb +2 -0
- data/lib/otto/response_handlers/factory.rb +2 -0
- data/lib/otto/response_handlers/json.rb +2 -0
- data/lib/otto/response_handlers/redirect.rb +2 -0
- data/lib/otto/response_handlers/view.rb +2 -0
- data/lib/otto/response_handlers.rb +2 -2
- data/lib/otto/route.rb +4 -4
- data/lib/otto/route_definition.rb +42 -15
- data/lib/otto/route_handlers/base.rb +2 -1
- data/lib/otto/route_handlers/class_method.rb +18 -25
- data/lib/otto/route_handlers/factory.rb +18 -16
- data/lib/otto/route_handlers/instance_method.rb +8 -5
- data/lib/otto/route_handlers/lambda.rb +8 -20
- data/lib/otto/route_handlers/logic_class.rb +25 -8
- data/lib/otto/route_handlers.rb +2 -2
- data/lib/otto/security/authentication/{failure_result.rb → auth_failure.rb} +5 -5
- data/lib/otto/security/authentication/auth_strategy.rb +13 -6
- data/lib/otto/security/authentication/route_auth_wrapper.rb +304 -41
- data/lib/otto/security/authentication/strategies/api_key_strategy.rb +2 -0
- data/lib/otto/security/authentication/strategies/noauth_strategy.rb +7 -1
- data/lib/otto/security/authentication/strategies/permission_strategy.rb +2 -0
- data/lib/otto/security/authentication/strategies/role_strategy.rb +2 -0
- data/lib/otto/security/authentication/strategies/session_strategy.rb +2 -0
- data/lib/otto/security/authentication/strategy_result.rb +6 -5
- data/lib/otto/security/authentication.rb +5 -6
- data/lib/otto/security/authorization_error.rb +73 -0
- data/lib/otto/security/config.rb +53 -9
- data/lib/otto/security/configurator.rb +17 -15
- data/lib/otto/security/csrf.rb +2 -2
- data/lib/otto/security/middleware/csrf_middleware.rb +11 -1
- data/lib/otto/security/middleware/ip_privacy_middleware.rb +231 -0
- data/lib/otto/security/middleware/rate_limit_middleware.rb +2 -0
- data/lib/otto/security/middleware/validation_middleware.rb +15 -0
- data/lib/otto/security/rate_limiter.rb +2 -2
- data/lib/otto/security/rate_limiting.rb +2 -2
- data/lib/otto/security/validator.rb +2 -2
- data/lib/otto/security.rb +12 -0
- data/lib/otto/static.rb +2 -2
- data/lib/otto/utils.rb +27 -2
- data/lib/otto/version.rb +3 -3
- data/lib/otto.rb +344 -89
- data/otto.gemspec +9 -2
- metadata +72 -8
- data/lib/otto/security/authentication/authentication_middleware.rb +0 -140
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 69a46d80f1fcc3f2472c44554fd7102645226e264befdd23af9871ad4f5fafaf
|
|
4
|
+
data.tar.gz: 54d8b433d49b549e17d3406aa011b6f4f80c50eefc5a6fa51dde1772fde8b8f2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b518140043ad7ab983fb99e85ac9643ca4527385930d77a4b01e2b241b7212854fbc5e8875bc58b17e3d17c66fcfc6455b85587d023ceec13006876bec79a0d9
|
|
7
|
+
data.tar.gz: f64b842b58acc746ad39d58eac0fb57c245f1db29755f89d259d64bd127db3043034a9e8ada0ee19c04d969ec1bb34b515f5dfc1fcf36c80ceaeb9123ac2de6e
|
data/.github/workflows/ci.yml
CHANGED
|
@@ -28,8 +28,6 @@ jobs:
|
|
|
28
28
|
fail-fast: false
|
|
29
29
|
matrix:
|
|
30
30
|
include:
|
|
31
|
-
- ruby: "3.2"
|
|
32
|
-
experimental: false
|
|
33
31
|
- ruby: "3.3"
|
|
34
32
|
experimental: false
|
|
35
33
|
- ruby: "3.4"
|
|
@@ -47,7 +45,7 @@ jobs:
|
|
|
47
45
|
bundler-cache: ${{ !matrix.experimental }}
|
|
48
46
|
|
|
49
47
|
- name: Setup tmate session
|
|
50
|
-
uses: mxschmitt/action-tmate@
|
|
48
|
+
uses: mxschmitt/action-tmate@c0afd6f790e3a5564914980036ebf83216678101 # v3
|
|
51
49
|
if: ${{ github.event_name == 'workflow_dispatch' && inputs.debug_enabled }}
|
|
52
50
|
with:
|
|
53
51
|
detached: true
|
|
@@ -2,13 +2,8 @@ name: Claude Code Review
|
|
|
2
2
|
|
|
3
3
|
on:
|
|
4
4
|
pull_request:
|
|
5
|
-
types: [opened, synchronize]
|
|
6
|
-
|
|
7
|
-
# paths:
|
|
8
|
-
# - "src/**/*.ts"
|
|
9
|
-
# - "src/**/*.tsx"
|
|
10
|
-
# - "src/**/*.js"
|
|
11
|
-
# - "src/**/*.jsx"
|
|
5
|
+
types: [opened, synchronize, labeled]
|
|
6
|
+
workflow_dispatch:
|
|
12
7
|
|
|
13
8
|
jobs:
|
|
14
9
|
claude-review:
|
|
@@ -19,6 +14,11 @@ jobs:
|
|
|
19
14
|
# github.event.pull_request.author_association == 'FIRST_TIME_CONTRIBUTOR'
|
|
20
15
|
|
|
21
16
|
runs-on: ubuntu-latest
|
|
17
|
+
if: |
|
|
18
|
+
(github.event.action == 'opened') ||
|
|
19
|
+
(github.event.action == 'labeled' && github.event.label.name == 'claude-review') ||
|
|
20
|
+
(github.event.action == 'synchronize' && contains(github.event.pull_request.labels.*.name, 'claude-review'))
|
|
21
|
+
|
|
22
22
|
permissions:
|
|
23
23
|
contents: read
|
|
24
24
|
pull-requests: read
|
|
@@ -33,10 +33,12 @@ jobs:
|
|
|
33
33
|
|
|
34
34
|
- name: Run Claude Code Review
|
|
35
35
|
id: claude-review
|
|
36
|
-
uses: anthropics/claude-code-action@
|
|
36
|
+
uses: anthropics/claude-code-action@beta
|
|
37
37
|
with:
|
|
38
38
|
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
39
|
-
|
|
39
|
+
|
|
40
|
+
# Direct prompt for automated review (no @claude mention needed)
|
|
41
|
+
direct_prompt: |
|
|
40
42
|
Please review this pull request and provide feedback on:
|
|
41
43
|
- Code quality and best practices
|
|
42
44
|
- Potential bugs or issues
|
|
@@ -46,8 +48,22 @@ jobs:
|
|
|
46
48
|
|
|
47
49
|
Use the repository's CLAUDE.md for guidance on style and conventions. Be constructive and helpful in your feedback.
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
# Use sticky comments to reuse the same comment on subsequent pushes to the same PR
|
|
52
|
+
use_sticky_comment: true
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
- name: Remove claude-review label
|
|
55
|
+
# Remove label whether success or failure - prevents getting stuck
|
|
56
|
+
if: always() && github.event.action != 'opened'
|
|
57
|
+
uses: actions/github-script@v8
|
|
58
|
+
with:
|
|
59
|
+
script: |
|
|
60
|
+
try {
|
|
61
|
+
await github.rest.issues.removeLabel({
|
|
62
|
+
owner: context.repo.owner,
|
|
63
|
+
repo: context.repo.repo,
|
|
64
|
+
issue_number: context.issue.number,
|
|
65
|
+
name: 'claude-review'
|
|
66
|
+
});
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.log('Label not found or already removed');
|
|
69
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# .github/workflows/code-smells.yml
|
|
2
|
+
---
|
|
3
|
+
name: Code Smells
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
push:
|
|
9
|
+
branches: [main]
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
pull-requests: write # Needed to post comments on PRs
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
reek-analysis:
|
|
18
|
+
name: Reek Code Analysis
|
|
19
|
+
runs-on: ubuntu-24.04
|
|
20
|
+
timeout-minutes: 5
|
|
21
|
+
|
|
22
|
+
steps:
|
|
23
|
+
- name: Checkout code
|
|
24
|
+
uses: actions/checkout@v5
|
|
25
|
+
|
|
26
|
+
- name: Set up Ruby
|
|
27
|
+
uses: ruby/setup-ruby@v1
|
|
28
|
+
with:
|
|
29
|
+
ruby-version: 3.4
|
|
30
|
+
bundler-cache: true
|
|
31
|
+
|
|
32
|
+
- name: Configure Bundler for secure gem installation
|
|
33
|
+
run: |
|
|
34
|
+
bundle config set --local path 'vendor/bundle'
|
|
35
|
+
bundle config set --local deployment 'false'
|
|
36
|
+
|
|
37
|
+
- name: Install dependencies
|
|
38
|
+
run: bundle install --jobs 4 --retry 3
|
|
39
|
+
|
|
40
|
+
- name: Run Reek analysis
|
|
41
|
+
run: |
|
|
42
|
+
echo "=== Running Reek code analysis ==="
|
|
43
|
+
echo "This analysis identifies code smells and potential improvements."
|
|
44
|
+
echo "Results are informational and won't fail the build."
|
|
45
|
+
echo ""
|
|
46
|
+
|
|
47
|
+
# Run reek and capture output (don't fail on warnings)
|
|
48
|
+
# Use success-exit-code to prevent failures from stopping the analysis
|
|
49
|
+
bundle exec reek --format=text --success-exit-code 0 --failure-exit-code 0 || true
|
|
50
|
+
|
|
51
|
+
echo ""
|
|
52
|
+
echo "=== Reek analysis complete ==="
|
|
53
|
+
continue-on-error: true # Don't fail the build on code smells
|
|
54
|
+
|
|
55
|
+
- name: Generate Reek report
|
|
56
|
+
run: |
|
|
57
|
+
echo "=== Generating detailed Reek report ==="
|
|
58
|
+
|
|
59
|
+
# Generate JSON report for potential future processing
|
|
60
|
+
bundle exec reek --format=json --success-exit-code 0 --failure-exit-code 0 > reek-report.json || true
|
|
61
|
+
|
|
62
|
+
# If no JSON was generated, create an empty valid JSON array
|
|
63
|
+
if [ ! -s reek-report.json ]; then
|
|
64
|
+
echo "[]" > reek-report.json
|
|
65
|
+
echo "No code smells detected - created empty report"
|
|
66
|
+
else
|
|
67
|
+
echo "Reek JSON report generated: $(wc -l < reek-report.json) lines"
|
|
68
|
+
echo "Top code smell types found:"
|
|
69
|
+
jq -r '.[].smells[].smell_type' reek-report.json 2>/dev/null | sort | uniq -c | sort -rn | head -10 || echo "Unable to parse JSON report"
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# Also generate a human-readable HTML report for easier viewing
|
|
73
|
+
bundle exec reek --format=html --success-exit-code 0 --failure-exit-code 0 > reek-report.html || echo "<!-- No code smells detected -->" > reek-report.html
|
|
74
|
+
continue-on-error: true
|
|
75
|
+
|
|
76
|
+
- name: Upload Reek report as artifact
|
|
77
|
+
uses: actions/upload-artifact@v5
|
|
78
|
+
if: always()
|
|
79
|
+
with:
|
|
80
|
+
name: reek-report
|
|
81
|
+
path: |
|
|
82
|
+
reek-report.json
|
|
83
|
+
reek-report.html
|
|
84
|
+
if-no-files-found: ignore
|
|
85
|
+
retention-days: 30
|
|
86
|
+
|
|
87
|
+
additional-checks:
|
|
88
|
+
name: Additional Quality Checks
|
|
89
|
+
runs-on: ubuntu-24.04
|
|
90
|
+
timeout-minutes: 5
|
|
91
|
+
|
|
92
|
+
steps:
|
|
93
|
+
- name: Checkout code
|
|
94
|
+
uses: actions/checkout@v5
|
|
95
|
+
|
|
96
|
+
- name: Set up Ruby
|
|
97
|
+
uses: ruby/setup-ruby@v1
|
|
98
|
+
with:
|
|
99
|
+
ruby-version: 3.4
|
|
100
|
+
bundler-cache: true
|
|
101
|
+
|
|
102
|
+
- name: Configure Bundler for secure gem installation
|
|
103
|
+
run: |
|
|
104
|
+
bundle config set --local path 'vendor/bundle'
|
|
105
|
+
bundle config set --local deployment 'false'
|
|
106
|
+
|
|
107
|
+
- name: Install dependencies
|
|
108
|
+
run: bundle install --jobs 4 --retry 3
|
|
109
|
+
|
|
110
|
+
- name: Check for TODO/FIXME comments
|
|
111
|
+
run: |
|
|
112
|
+
echo "=== Scanning for TODO/FIXME comments ==="
|
|
113
|
+
echo "This helps track technical debt and action items."
|
|
114
|
+
echo ""
|
|
115
|
+
|
|
116
|
+
# Find TODO/FIXME comments (excluding vendor and tmp directories)
|
|
117
|
+
find . -name "*.rb" -not -path "./vendor/*" -not -path "./tmp/*" | \
|
|
118
|
+
xargs grep -Hn -i -E "(TODO|FIXME|HACK|XXX|NOTE):" 2>/dev/null | \
|
|
119
|
+
head -20 || echo "No TODO/FIXME comments found"
|
|
120
|
+
continue-on-error: true
|
|
121
|
+
|
|
122
|
+
- name: Check Ruby file syntax
|
|
123
|
+
run: |
|
|
124
|
+
echo "=== Checking Ruby syntax ==="
|
|
125
|
+
echo "Validates that all Ruby files have correct syntax."
|
|
126
|
+
echo ""
|
|
127
|
+
|
|
128
|
+
find . -name "*.rb" -not -path "./vendor/*" -not -path "./tmp/*" | \
|
|
129
|
+
while read -r file; do
|
|
130
|
+
if ! ruby -c "$file" > /dev/null 2>&1; then
|
|
131
|
+
echo "Syntax error in: $file"
|
|
132
|
+
ruby -c "$file"
|
|
133
|
+
fi
|
|
134
|
+
done
|
|
135
|
+
continue-on-error: true
|
|
136
|
+
|
|
137
|
+
- name: Check for long lines
|
|
138
|
+
run: |
|
|
139
|
+
echo "=== Checking for long lines (>120 characters) ==="
|
|
140
|
+
echo "Identifies potentially hard-to-read code lines."
|
|
141
|
+
echo ""
|
|
142
|
+
|
|
143
|
+
find . -name "*.rb" -not -path "./vendor/*" -not -path "./tmp/*" | \
|
|
144
|
+
xargs grep -Hn "^.\{121,\}$" | \
|
|
145
|
+
head -10 || echo "No overly long lines found"
|
|
146
|
+
continue-on-error: true
|
data/.gitignore
CHANGED
data/.pre-commit-config.yaml
CHANGED
|
@@ -96,12 +96,12 @@ repos:
|
|
|
96
96
|
|
|
97
97
|
# Commit message issue tracking integration
|
|
98
98
|
- repo: https://github.com/avilaton/add-msg-issue-prefix-hook
|
|
99
|
-
rev: v0.0.
|
|
99
|
+
rev: v0.0.13
|
|
100
100
|
hooks:
|
|
101
101
|
- id: add-msg-issue-prefix
|
|
102
102
|
stages: [prepare-commit-msg]
|
|
103
103
|
description: Automatically prefix commits with issue numbers
|
|
104
104
|
args:
|
|
105
105
|
- "--default="
|
|
106
|
-
-
|
|
106
|
+
- "--pattern=(i18n(?=/)|([a-zA-Z0-9]{0,10}-?[0-9]{1,5}))"
|
|
107
107
|
- "--template=[#{}]"
|
data/.reek.yml
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# .reek.yml
|
|
2
|
+
#
|
|
3
|
+
# Reek configuration for Otto
|
|
4
|
+
#
|
|
5
|
+
# Basic commands:
|
|
6
|
+
# bundle exec reek # Analyze all Ruby files
|
|
7
|
+
# bundle exec reek lib/ # Analyze specific directory
|
|
8
|
+
# bundle exec reek lib/otto.rb # Analyze specific file
|
|
9
|
+
# bundle exec reek --help # Show all options
|
|
10
|
+
# bundle exec reek --docs # Open documentation
|
|
11
|
+
#
|
|
12
|
+
# Advanced usage:
|
|
13
|
+
# bundle exec reek --format=html > report.html # Generate HTML report
|
|
14
|
+
# bundle exec reek --format=json # JSON output for CI
|
|
15
|
+
# bundle exec reek --config .reek.yml # Use specific config
|
|
16
|
+
# bundle exec reek --show-docs IrresponsibleModule # Explain specific smell
|
|
17
|
+
# bundle exec reek --failure-exit-code 1 # Exit with error on smells (for CI)
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
detectors:
|
|
21
|
+
# Disable some detectors for initial adoption
|
|
22
|
+
# You can gradually enable these as you clean up the codebase
|
|
23
|
+
|
|
24
|
+
# Class/Module Structure
|
|
25
|
+
IrresponsibleModule:
|
|
26
|
+
enabled: false # Modules without documentation - start with this disabled
|
|
27
|
+
|
|
28
|
+
# Method Complexity
|
|
29
|
+
TooManyStatements:
|
|
30
|
+
enabled: true
|
|
31
|
+
max_statements: 15 # Default is 5, relaxed for initial adoption
|
|
32
|
+
|
|
33
|
+
TooManyMethods:
|
|
34
|
+
enabled: true
|
|
35
|
+
max_methods: 25 # Default is 15, relaxed for ORMs which often have many methods
|
|
36
|
+
|
|
37
|
+
LongParameterList:
|
|
38
|
+
enabled: true
|
|
39
|
+
max_params: 4 # Default is 3, slightly relaxed
|
|
40
|
+
|
|
41
|
+
# Data Classes and Feature Envy
|
|
42
|
+
DataClump:
|
|
43
|
+
enabled: true
|
|
44
|
+
|
|
45
|
+
FeatureEnvy:
|
|
46
|
+
enabled: true
|
|
47
|
+
|
|
48
|
+
# Control Structure
|
|
49
|
+
NestedIterators:
|
|
50
|
+
enabled: true
|
|
51
|
+
max_allowed_nesting: 2 # Default is 1, relaxed for data processing
|
|
52
|
+
|
|
53
|
+
# Variable and Constant Usage
|
|
54
|
+
UnusedParameters:
|
|
55
|
+
enabled: true
|
|
56
|
+
|
|
57
|
+
InstanceVariableAssumption:
|
|
58
|
+
enabled: true
|
|
59
|
+
|
|
60
|
+
# Naming
|
|
61
|
+
UncommunicativeParameterName:
|
|
62
|
+
enabled: true
|
|
63
|
+
reject:
|
|
64
|
+
- "/^.$/" # Single letter names
|
|
65
|
+
- "/[0-9]$/" # Names ending in numbers
|
|
66
|
+
- "/^_/" # Names starting with underscore (common Ruby pattern)
|
|
67
|
+
accept: []
|
|
68
|
+
|
|
69
|
+
UncommunicativeVariableName:
|
|
70
|
+
enabled: true
|
|
71
|
+
reject:
|
|
72
|
+
- "/^.$/" # Single letter names
|
|
73
|
+
- "/[0-9]$/" # Names ending in numbers
|
|
74
|
+
accept:
|
|
75
|
+
- e # Exception variable
|
|
76
|
+
- id # Common identifier
|
|
77
|
+
- db # Database connection
|
|
78
|
+
- op # Operation
|
|
79
|
+
- io # Input/output
|
|
80
|
+
|
|
81
|
+
UncommunicativeMethodName:
|
|
82
|
+
enabled: true
|
|
83
|
+
reject:
|
|
84
|
+
- "/^.$/" # Single letter method names
|
|
85
|
+
- "/[0-9]$/" # Methods ending in numbers
|
|
86
|
+
accept:
|
|
87
|
+
- "<<" # Common Ruby operator overload
|
|
88
|
+
|
|
89
|
+
# Directory and file exclusions
|
|
90
|
+
exclude_paths:
|
|
91
|
+
- "vendor/**/*.rb"
|
|
92
|
+
- "tmp/**/*.rb"
|
|
93
|
+
- "try/**/*.rb" # Test files using tryouts framework
|
|
94
|
+
- "examples/**/*.rb" # Example code files
|
|
95
|
+
- "bin/*" # Executable scripts
|
|
96
|
+
- "*.gemspec" # Gem specification files
|
|
97
|
+
|
|
98
|
+
# Note: For limiting warnings output, use CLI: bundle exec reek | head -50
|
|
99
|
+
# Note: For failure exit codes, use CLI: bundle exec reek --failure-exit-code 1
|
data/CHANGELOG.rst
CHANGED
|
@@ -7,6 +7,95 @@ The format is based on `Keep a Changelog <https://keepachangelog.com/en/1.1.0/>`
|
|
|
7
7
|
|
|
8
8
|
<!--scriv-insert-here-->
|
|
9
9
|
|
|
10
|
+
.. _changelog-2.0.0.pre6:
|
|
11
|
+
|
|
12
|
+
2.0.0.pre6 — TBD
|
|
13
|
+
================
|
|
14
|
+
|
|
15
|
+
Changed
|
|
16
|
+
-------
|
|
17
|
+
|
|
18
|
+
- **BREAKING**: ``Otto.on_request_complete`` is now an instance method instead of a class method. This fixes duplicate callback invocations in multi-app architectures (e.g., Rack::URLMap with multiple Otto instances). Each Otto instance now maintains its own isolated set of callbacks that only fire for requests processed by that specific instance.
|
|
19
|
+
|
|
20
|
+
**Migration**: Change ``Otto.on_request_complete { |req, res, dur| ... }`` to ``otto.on_request_complete { |req, res, dur| ... }``
|
|
21
|
+
|
|
22
|
+
- **Logging**: Eliminated duplicate error logging in route handlers. Previously, errors produced two log lines ("Handler execution failed" + "Unhandled error in request"). Now produces a single comprehensive error log with all context (handler, duration, error_id). Lambda handlers now use centralized error handling for consistency. #86
|
|
23
|
+
|
|
24
|
+
Fixed
|
|
25
|
+
-----
|
|
26
|
+
|
|
27
|
+
- Fixed issue #84 where ``on_request_complete`` callbacks would fire N times per request in multi-app architectures, causing duplicate logging and metrics
|
|
28
|
+
- Fixed ``Otto.structured_log`` to respect ``Otto.debug`` flag - debug logs are now properly skipped when ``Otto.debug = false``
|
|
29
|
+
|
|
30
|
+
AI Assistance
|
|
31
|
+
-------------
|
|
32
|
+
|
|
33
|
+
- This enhancement was developed with assistance from Claude Code (Opus 4.1)
|
|
34
|
+
|
|
35
|
+
.. _changelog-2.0.0.pre5:
|
|
36
|
+
|
|
37
|
+
2.0.0.pre5 — 2025-10-21
|
|
38
|
+
=======================
|
|
39
|
+
|
|
40
|
+
Added
|
|
41
|
+
-----
|
|
42
|
+
|
|
43
|
+
- Added ``Otto::LoggingHelpers.log_timed_operation`` for automatic timing and error handling of operations
|
|
44
|
+
- Added ``Otto::LoggingHelpers.log_backtrace`` for consistent backtrace logging with correlation fields
|
|
45
|
+
- Added microsecond-precision timing to configuration freeze process
|
|
46
|
+
- Added unique error ID generation for nested error handler failures (links via ``original_error_id``)
|
|
47
|
+
|
|
48
|
+
Changed
|
|
49
|
+
-------
|
|
50
|
+
|
|
51
|
+
- Timing precision standardization: All timing calculations now use microsecond precision instead of milliseconds. This affects authentication duration tracking and request lifecycle timing. Duration values are now reported in microseconds as integers (e.g., ``15200`` instead of ``15.2``).
|
|
52
|
+
- Request completion hooks API improvement: ``Otto.on_request_complete`` callbacks now receive a ``Rack::Response`` object instead of the raw ``[status, headers, body]`` tuple. This provides a more developer-friendly API consistent with ``Rack::Request``, allowing clean access via ``res.status``, ``res.headers``, and ``res.body`` instead of array indexing.
|
|
53
|
+
- All timing now uses microseconds (``Otto::Utils.now_in_μs``) for consistency
|
|
54
|
+
- Configuration freeze process now logs detailed timing metrics
|
|
55
|
+
|
|
56
|
+
Documentation
|
|
57
|
+
-------------
|
|
58
|
+
|
|
59
|
+
- Added example application demonstrating three new logging patterns (``examples/logging_improvements.rb``)
|
|
60
|
+
- Documented base context pattern for downstream projects to inject custom correlation fields
|
|
61
|
+
- Added output examples for both structured and standard loggers
|
|
62
|
+
|
|
63
|
+
AI Assistance
|
|
64
|
+
-------------
|
|
65
|
+
|
|
66
|
+
- This enhancement was developed with assistance from Claude Code (Opus 4.1)
|
|
67
|
+
|
|
68
|
+
.. _changelog-2.0.0.pre4:
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
2.0.0.pre4 — 2025-10-20
|
|
72
|
+
=======================
|
|
73
|
+
Changed
|
|
74
|
+
-------
|
|
75
|
+
- Authentication moved from middleware to RouteAuthWrapper at handler level (executes after routing)
|
|
76
|
+
- RouteAuthWrapper now wraps all routes and provides session persistence, security headers, strategy caching, and pattern matching (exact, prefix, fallback)
|
|
77
|
+
- env['otto.strategy_result'] now guaranteed present on all routes (authenticated or anonymous)
|
|
78
|
+
- Renamed MiddlewareStack#build_app to #wrap (reflects per-request wrapping vs one-time initialization)
|
|
79
|
+
|
|
80
|
+
Removed
|
|
81
|
+
-------
|
|
82
|
+
- AuthenticationMiddleware (executed before routing)
|
|
83
|
+
- enable_authentication! (RouteAuthWrapper handles auth automatically)
|
|
84
|
+
- Defensive nil fallback from LogicClassHandler (no longer needed)
|
|
85
|
+
|
|
86
|
+
Fixed
|
|
87
|
+
-----
|
|
88
|
+
- Session persistence: env['rack.session'] now references same object as strategy_result.session
|
|
89
|
+
- Security headers included on all auth failure responses (401/302)
|
|
90
|
+
- Anonymous routes now receive StrategyResult with IP metadata
|
|
91
|
+
|
|
92
|
+
Documentation
|
|
93
|
+
-------------
|
|
94
|
+
- Updated CLAUDE.md with RouteAuthWrapper architecture
|
|
95
|
+
- Updated env_keys.rb to document strategy_result guarantee
|
|
96
|
+
- Added tests for anonymous route handling
|
|
97
|
+
|
|
98
|
+
|
|
10
99
|
.. _changelog-2.0.0.pre2:
|
|
11
100
|
|
|
12
101
|
2.0.0.pre2 — 2025-10-11
|
|
@@ -60,6 +149,7 @@ AI Assistance
|
|
|
60
149
|
- Comprehensive migration of Logic classes and documentation with AI guidance for consistency
|
|
61
150
|
- Automated test validation and intelligent file organization following Ruby conventions
|
|
62
151
|
|
|
152
|
+
|
|
63
153
|
.. _changelog-2.0.0-pre1:
|
|
64
154
|
|
|
65
155
|
2.0.0-pre1 — 2025-09-10
|
data/CLAUDE.md
CHANGED
|
@@ -1,56 +1,127 @@
|
|
|
1
1
|
# CLAUDE.md
|
|
2
2
|
|
|
3
|
-
This file provides guidance to Claude Code
|
|
3
|
+
This file provides essential guidance to Claude Code when working with Otto.
|
|
4
|
+
|
|
5
|
+
## Error Handler Registration
|
|
6
|
+
|
|
7
|
+
Register handlers for expected business logic errors to avoid logging them as 500 errors:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
otto = Otto.new('routes.txt')
|
|
11
|
+
otto.register_error_handler(YourApp::NotFound, status: 404, log_level: :info)
|
|
12
|
+
otto.register_error_handler(YourApp::RateLimited, status: 429, log_level: :warn)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Must be registered before first request (before configuration freezing).
|
|
16
|
+
|
|
17
|
+
## Authentication Architecture
|
|
18
|
+
|
|
19
|
+
Authentication is handled by `RouteAuthWrapper` at the handler level, NOT by middleware.
|
|
20
|
+
|
|
21
|
+
### Basic Configuration
|
|
22
|
+
|
|
23
|
+
```ruby
|
|
24
|
+
otto.add_auth_strategy('session', SessionStrategy.new)
|
|
25
|
+
otto.add_auth_strategy('apikey', APIKeyStrategy.new)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- Strategy names must be unique
|
|
29
|
+
- Routes with `auth` requirements are automatically wrapped
|
|
30
|
+
- Must be configured before first request
|
|
31
|
+
|
|
32
|
+
### Multi-Strategy Authentication
|
|
33
|
+
|
|
34
|
+
Routes support multiple strategies with OR logic:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
# Routes file
|
|
38
|
+
GET /api/data DataLogic#show auth=session,apikey,oauth
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- Strategies execute left-to-right
|
|
42
|
+
- First success wins (remaining strategies skipped)
|
|
43
|
+
- Returns 401 only if all strategies fail
|
|
44
|
+
- Put fastest/most-common strategies first
|
|
45
|
+
|
|
46
|
+
### Two-Layer Authorization
|
|
47
|
+
|
|
48
|
+
**Layer 1: Route-Level (RouteAuthWrapper)**
|
|
49
|
+
- Use `auth=` for authentication strategies
|
|
50
|
+
- Use `role=` for role-based access (OR logic: `role=admin,editor`)
|
|
51
|
+
- Fast execution (no database queries)
|
|
52
|
+
- Returns 401 (authentication) or 403 (authorization)
|
|
53
|
+
|
|
54
|
+
**Layer 2: Resource-Level (Logic classes)**
|
|
55
|
+
- Handled in `raise_concerns` method
|
|
56
|
+
- Checks ownership, relationships, resource attributes
|
|
57
|
+
- Raises `Otto::Security::AuthorizationError` for 403 response
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
# Route-level
|
|
61
|
+
GET /admin/users AdminLogic auth=session role=admin
|
|
62
|
+
|
|
63
|
+
# Resource-level in Logic class
|
|
64
|
+
def raise_concerns
|
|
65
|
+
@post = Post.find(params[:id])
|
|
66
|
+
unless @post.user_id == @context.user_id
|
|
67
|
+
raise Otto::Security::AuthorizationError, "Cannot edit another user's post"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Configuration Freezing
|
|
73
|
+
|
|
74
|
+
Otto automatically freezes all configuration after first request to prevent runtime security bypasses. Multi-step initialization must complete before first request.
|
|
75
|
+
|
|
76
|
+
## IP Privacy (Privacy by Default)
|
|
77
|
+
|
|
78
|
+
Otto automatically masks public IP addresses while preserving private/localhost IPs for development:
|
|
79
|
+
|
|
80
|
+
- `IPPrivacyMiddleware` runs FIRST in middleware stack
|
|
81
|
+
- Replaces `env` values directly (REMOTE_ADDR, HTTP_USER_AGENT, HTTP_REFERER)
|
|
82
|
+
- Public IPs masked (192.0.2.100 → 192.0.2.0)
|
|
83
|
+
- Private IPs never masked (127.0.0.1, 192.168.x.x, 10.x.x.x)
|
|
84
|
+
- Supports proxy resolution with trusted proxy configuration
|
|
85
|
+
|
|
86
|
+
For multi-app architectures, add to common middleware stack before logging/monitoring.
|
|
87
|
+
|
|
88
|
+
## Structured Logging
|
|
89
|
+
|
|
90
|
+
Use explicit structured logging with timing:
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
Otto.structured_log(:debug, "Route matched",
|
|
94
|
+
Otto::LoggingHelpers.request_context(env).merge(
|
|
95
|
+
type: 'literal',
|
|
96
|
+
handler: route.definition
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# For timed operations
|
|
101
|
+
Otto::LoggingHelpers.log_timed_operation(:info, "Operation", env, key: value) do
|
|
102
|
+
perform_operation()
|
|
103
|
+
end
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- All timing in microseconds via `Otto::Utils.now_in_μs`
|
|
107
|
+
- Use `request_context(env).merge()` pattern for consistency
|
|
108
|
+
- Avoid abstraction layers or event classes
|
|
4
109
|
|
|
5
110
|
## Development Commands
|
|
6
111
|
|
|
7
|
-
### Setup
|
|
8
112
|
```bash
|
|
9
|
-
# Install development and test dependencies
|
|
10
|
-
bundle config set with 'development test'
|
|
11
113
|
bundle install
|
|
12
|
-
|
|
13
|
-
# Lint code
|
|
14
114
|
bundle exec rubocop
|
|
15
|
-
|
|
16
|
-
# Run tests
|
|
17
115
|
bundle exec rspec
|
|
18
|
-
|
|
19
|
-
# Run a specific test
|
|
20
|
-
bundle exec rspec spec/path/to/specific_spec.rb
|
|
21
|
-
# rspec settings in .rspec
|
|
22
116
|
```
|
|
23
117
|
|
|
24
|
-
##
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- Optional security features:
|
|
35
|
-
- CSRF protection
|
|
36
|
-
- Input validation
|
|
37
|
-
- Security headers
|
|
38
|
-
- Trusted proxy configuration
|
|
39
|
-
|
|
40
|
-
### Test Frameworks
|
|
41
|
-
- RSpec for unit and integration testing
|
|
42
|
-
- Tryouts for behavior-driven testing
|
|
43
|
-
|
|
44
|
-
### Development Tools
|
|
45
|
-
- Rubocop for linting
|
|
46
|
-
- Debug gem for debugging
|
|
47
|
-
- Tryouts for alternative testing approach
|
|
48
|
-
|
|
49
|
-
### Ruby Version Requirements
|
|
50
|
-
- Ruby 3.2+
|
|
51
|
-
- Rack 3.1+
|
|
52
|
-
|
|
53
|
-
### Important Notes
|
|
54
|
-
- Always validate and sanitize user inputs
|
|
55
|
-
- Leverage built-in security features
|
|
56
|
-
- Use locale helpers for internationalization support
|
|
118
|
+
## Key Architecture Principles
|
|
119
|
+
|
|
120
|
+
- **Security by Default**: IP privacy, configuration freezing, backtrace sanitization
|
|
121
|
+
- **Privacy by Default**: Public IP masking, no original value storage
|
|
122
|
+
- **Explicit over Implicit**: Direct logging calls, clear configuration
|
|
123
|
+
- **Handler-Level Auth**: Not middleware-based authentication
|
|
124
|
+
- **Two-Layer Authorization**: Route-level + resource-level separation
|
|
125
|
+
- **Rack Integration**: Standard Rack patterns and compatibility
|
|
126
|
+
|
|
127
|
+
See `docs/` directory for comprehensive documentation.
|
data/Gemfile
CHANGED
|
@@ -14,6 +14,7 @@ gem 'rackup'
|
|
|
14
14
|
group :test do
|
|
15
15
|
gem 'rack-test'
|
|
16
16
|
gem 'rspec', '~> 3.13'
|
|
17
|
+
gem 'user_agent_parser', '~> 2.18' # Validate anonymized UAs preserve semantic info
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
# bundle config set with 'optional'
|
|
@@ -21,16 +22,18 @@ group :development, :test, optional: true do
|
|
|
21
22
|
# Keep gems that need to be in both environments
|
|
22
23
|
gem 'json_schemer'
|
|
23
24
|
gem 'rack-attack'
|
|
25
|
+
gem 'reek', '~> 6.5'
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
group :development do
|
|
29
|
+
gem 'benchmark'
|
|
27
30
|
gem 'debug'
|
|
28
|
-
gem 'rubocop', '~> 1.81.
|
|
31
|
+
gem 'rubocop', '~> 1.81.7', require: false
|
|
29
32
|
gem 'rubocop-performance', require: false
|
|
30
33
|
gem 'rubocop-rspec', require: false
|
|
31
34
|
gem 'rubocop-thread_safety', require: false
|
|
32
35
|
gem 'ruby-lsp', require: false
|
|
33
36
|
gem 'stackprof', require: false
|
|
34
37
|
gem 'syntax_tree', require: false
|
|
35
|
-
gem 'tryouts', '~> 3.
|
|
38
|
+
gem 'tryouts', '~> 3.7.1', require: false
|
|
36
39
|
end
|