otto 1.1.0.pre.alpha4 → 1.2.0
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/.gitignore +2 -0
- data/.rspec +4 -0
- data/.rubocop.yml +11 -9
- data/.rubocop_todo.yml +152 -0
- data/Gemfile +16 -5
- data/Gemfile.lock +49 -3
- data/LICENSE.txt +1 -1
- data/README.md +61 -112
- data/VERSION.yml +3 -2
- data/examples/basic/app.rb +78 -0
- data/examples/basic/config.ru +30 -0
- data/examples/basic/routes +20 -0
- data/examples/dynamic_pages/app.rb +115 -0
- data/examples/dynamic_pages/config.ru +30 -0
- data/{example → examples/dynamic_pages}/routes +5 -3
- data/examples/security_features/app.rb +273 -0
- data/examples/security_features/config.ru +81 -0
- data/examples/security_features/routes +11 -0
- data/lib/otto/design_system.rb +463 -0
- data/lib/otto/helpers/request.rb +126 -15
- data/lib/otto/helpers/response.rb +99 -13
- data/lib/otto/route.rb +105 -13
- data/lib/otto/security/config.rb +316 -0
- data/lib/otto/security/csrf.rb +181 -0
- data/lib/otto/security/validator.rb +296 -0
- data/lib/otto/static.rb +18 -5
- data/lib/otto/version.rb +6 -4
- data/lib/otto.rb +330 -116
- data/otto.gemspec +13 -12
- metadata +41 -18
- data/CHANGES.txt +0 -35
- data/example/app.rb +0 -58
- data/example/config.ru +0 -35
- /data/{example/public → public}/favicon.ico +0 -0
- /data/{example/public → public}/img/otto.jpg +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c17a841334ba79be6d1c4625eb8e8e39eda301be3a77813c8ea6386bb3d0d6b5
|
4
|
+
data.tar.gz: 2639d6f7ef18aa85bf75646d5c9d97814d1bf3a585e53003de7ccb4e8ce1eccf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 40e9f5ed072e4d86dd27c49d397fa3ece8904420a3264166ad187aeea9c26c3818c32c45986903914df664cbb7671c8edabad18601f60013c8f05d2ce742d65f
|
7
|
+
data.tar.gz: 95d85cabd00d26658ce816021289ca7a21600dee34ac051ccfa6cf79b8713742d5389e67042d49991a1a63794fe30e3cd76d11bfb7d3ee52a8281e42e184174b
|
data/.gitignore
CHANGED
data/.rspec
ADDED
data/.rubocop.yml
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# .rubocop.yml
|
2
|
+
|
1
3
|
##
|
2
4
|
# This is the RuboCop configuration file.
|
3
5
|
# It contains the rules and settings for the RuboCop linter.
|
@@ -13,7 +15,7 @@
|
|
13
15
|
#
|
14
16
|
inherit_from: .rubocop_todo.yml
|
15
17
|
|
16
|
-
|
18
|
+
plugins:
|
17
19
|
- rubocop-performance
|
18
20
|
- rubocop-thread_safety
|
19
21
|
|
@@ -21,13 +23,13 @@ AllCops:
|
|
21
23
|
NewCops: enable
|
22
24
|
UseCache: true
|
23
25
|
MaxFilesInCache: 100
|
24
|
-
TargetRubyVersion: 3.
|
26
|
+
TargetRubyVersion: 3.4
|
25
27
|
Exclude:
|
26
|
-
-
|
27
|
-
-
|
28
|
-
-
|
29
|
-
-
|
30
|
-
-
|
28
|
+
- "migrate/**/*.rb"
|
29
|
+
- "migrate/*.rb"
|
30
|
+
- "try/**/*"
|
31
|
+
- "try/*.rb"
|
32
|
+
- "vendor/**/*"
|
31
33
|
|
32
34
|
Gemspec/DeprecatedAttributeAssignment:
|
33
35
|
Enabled: true
|
@@ -55,12 +57,12 @@ Metrics/CyclomaticComplexity:
|
|
55
57
|
Metrics/MethodLength:
|
56
58
|
Enabled: true
|
57
59
|
Max: 40
|
58
|
-
CountAsOne: [
|
60
|
+
CountAsOne: ["method_call"]
|
59
61
|
|
60
62
|
Metrics/ModuleLength:
|
61
63
|
Enabled: true
|
62
64
|
Max: 250
|
63
|
-
CountAsOne: [
|
65
|
+
CountAsOne: ["method_call"]
|
64
66
|
|
65
67
|
Performance/Size:
|
66
68
|
Enabled: true
|
data/.rubocop_todo.yml
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
# This configuration was generated by
|
2
|
+
# `rubocop --auto-gen-config`
|
3
|
+
# on 2025-07-18 21:29:04 UTC using RuboCop version 1.78.0.
|
4
|
+
# The point is for the user to remove these configuration records
|
5
|
+
# one by one as the offenses are removed from the code base.
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
8
|
+
|
9
|
+
# Offense count: 2
|
10
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
11
|
+
# Configuration parameters: AllowSafeAssignment.
|
12
|
+
Lint/AssignmentInCondition:
|
13
|
+
Exclude:
|
14
|
+
- 'lib/otto.rb'
|
15
|
+
|
16
|
+
# Offense count: 1
|
17
|
+
# Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns.
|
18
|
+
Metrics/MethodLength:
|
19
|
+
Exclude:
|
20
|
+
- 'lib/otto.rb'
|
21
|
+
|
22
|
+
# Offense count: 5
|
23
|
+
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
24
|
+
Metrics/PerceivedComplexity:
|
25
|
+
Max: 36
|
26
|
+
|
27
|
+
# Offense count: 1
|
28
|
+
Security/Eval:
|
29
|
+
Exclude:
|
30
|
+
- 'lib/otto/route.rb'
|
31
|
+
|
32
|
+
# Offense count: 6
|
33
|
+
# Configuration parameters: AllowedConstants.
|
34
|
+
Style/Documentation:
|
35
|
+
Exclude:
|
36
|
+
- 'spec/**/*'
|
37
|
+
- 'test/**/*'
|
38
|
+
- 'example/app.rb'
|
39
|
+
- 'lib/otto.rb'
|
40
|
+
- 'lib/otto/helpers/request.rb'
|
41
|
+
- 'lib/otto/helpers/response.rb'
|
42
|
+
- 'lib/otto/route.rb'
|
43
|
+
- 'lib/otto/static.rb'
|
44
|
+
|
45
|
+
# Offense count: 9
|
46
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
47
|
+
# Configuration parameters: EnforcedStyle.
|
48
|
+
# SupportedStyles: always, always_true, never
|
49
|
+
Style/FrozenStringLiteralComment:
|
50
|
+
Exclude:
|
51
|
+
- '**/*.arb'
|
52
|
+
- 'example/app.rb'
|
53
|
+
- 'example/config.ru'
|
54
|
+
- 'lib/otto.rb'
|
55
|
+
- 'lib/otto/helpers/request.rb'
|
56
|
+
- 'lib/otto/helpers/response.rb'
|
57
|
+
- 'lib/otto/route.rb'
|
58
|
+
- 'lib/otto/static.rb'
|
59
|
+
- 'lib/otto/version.rb'
|
60
|
+
- 'otto.gemspec'
|
61
|
+
|
62
|
+
# Offense count: 1
|
63
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
64
|
+
# Configuration parameters: EnforcedStyle, Autocorrect.
|
65
|
+
# SupportedStyles: module_function, extend_self, forbidden
|
66
|
+
Style/ModuleFunction:
|
67
|
+
Exclude:
|
68
|
+
- 'lib/otto/static.rb'
|
69
|
+
|
70
|
+
# Offense count: 2
|
71
|
+
Style/MultilineBlockChain:
|
72
|
+
Exclude:
|
73
|
+
- 'lib/otto.rb'
|
74
|
+
|
75
|
+
# Offense count: 2
|
76
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
77
|
+
# Configuration parameters: EnforcedStyle.
|
78
|
+
# SupportedStyles: literals, strict
|
79
|
+
Style/MutableConstant:
|
80
|
+
Exclude:
|
81
|
+
- 'example/config.ru'
|
82
|
+
|
83
|
+
# Offense count: 1
|
84
|
+
# Configuration parameters: AllowedMethods.
|
85
|
+
# AllowedMethods: respond_to_missing?
|
86
|
+
Style/OptionalBooleanParameter:
|
87
|
+
Exclude:
|
88
|
+
- 'lib/otto/helpers/response.rb'
|
89
|
+
|
90
|
+
# Offense count: 1
|
91
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
92
|
+
# Configuration parameters: EnforcedStyle.
|
93
|
+
# SupportedStyles: short, verbose
|
94
|
+
Style/PreferredHashMethods:
|
95
|
+
Exclude:
|
96
|
+
- 'lib/otto.rb'
|
97
|
+
|
98
|
+
# Offense count: 1
|
99
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
100
|
+
Style/RedundantFilterChain:
|
101
|
+
Exclude:
|
102
|
+
- 'lib/otto.rb'
|
103
|
+
|
104
|
+
# Offense count: 1
|
105
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
106
|
+
Style/RedundantInterpolation:
|
107
|
+
Exclude:
|
108
|
+
- 'example/config.ru'
|
109
|
+
|
110
|
+
# Offense count: 1
|
111
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
112
|
+
Style/SelectByRegexp:
|
113
|
+
Exclude:
|
114
|
+
- 'lib/otto.rb'
|
115
|
+
|
116
|
+
# Offense count: 1
|
117
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
118
|
+
Style/SlicingWithRange:
|
119
|
+
Exclude:
|
120
|
+
- 'lib/otto/route.rb'
|
121
|
+
|
122
|
+
# Offense count: 4
|
123
|
+
# This cop supports unsafe autocorrection (--autocorrect-all).
|
124
|
+
# Configuration parameters: RequireEnglish.
|
125
|
+
# SupportedStyles: use_perl_names, use_english_names, use_builtin_english_names
|
126
|
+
Style/SpecialGlobalVars:
|
127
|
+
EnforcedStyle: use_perl_names
|
128
|
+
|
129
|
+
# Offense count: 1
|
130
|
+
# Configuration parameters: ActiveSupportClassAttributeAllowed.
|
131
|
+
ThreadSafety/ClassAndModuleAttributes:
|
132
|
+
Exclude:
|
133
|
+
- 'lib/otto.rb'
|
134
|
+
|
135
|
+
# Offense count: 9
|
136
|
+
ThreadSafety/ClassInstanceVariable:
|
137
|
+
Exclude:
|
138
|
+
- 'lib/otto.rb'
|
139
|
+
- 'lib/otto/version.rb'
|
140
|
+
|
141
|
+
# Offense count: 1
|
142
|
+
# Configuration parameters: AllowCallWithBlock.
|
143
|
+
ThreadSafety/DirChdir:
|
144
|
+
Exclude:
|
145
|
+
- 'otto.gemspec'
|
146
|
+
|
147
|
+
# Offense count: 1
|
148
|
+
# This cop supports safe autocorrection (--autocorrect).
|
149
|
+
# Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
|
150
|
+
# URISchemes: http, https
|
151
|
+
Layout/LineLength:
|
152
|
+
Max: 135
|
data/Gemfile
CHANGED
@@ -1,11 +1,22 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
source
|
3
|
+
source 'https://rubygems.org'
|
4
4
|
|
5
5
|
gemspec
|
6
6
|
|
7
|
-
group
|
8
|
-
gem
|
9
|
-
gem
|
10
|
-
|
7
|
+
group :development, :test do
|
8
|
+
gem 'rspec', '~> 3.12'
|
9
|
+
gem 'rack-test'
|
10
|
+
end
|
11
|
+
|
12
|
+
group 'development' do
|
13
|
+
gem 'pry-byebug', require: false
|
14
|
+
gem 'rubocop', require: false
|
15
|
+
gem 'rubocop-performance', require: false
|
16
|
+
gem 'rubocop-rspec', require: false
|
17
|
+
gem 'rubocop-thread_safety', require: false
|
18
|
+
gem 'ruby-lsp', require: false
|
19
|
+
gem 'stackprof', require: false
|
20
|
+
gem 'syntax_tree', require: false
|
21
|
+
gem 'tryouts', require: false
|
11
22
|
end
|
data/Gemfile.lock
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
otto (1.
|
4
|
+
otto (1.2.0)
|
5
5
|
addressable (~> 2.2, < 3)
|
6
|
-
rack (~>
|
6
|
+
rack (~> 3.1, < 4.0)
|
7
|
+
rack-parser (~> 0.7)
|
7
8
|
rexml (>= 3.3.6)
|
8
9
|
|
9
10
|
GEM
|
@@ -14,14 +15,18 @@ GEM
|
|
14
15
|
ast (2.4.2)
|
15
16
|
byebug (11.1.3)
|
16
17
|
coderay (1.1.3)
|
18
|
+
diff-lcs (1.6.2)
|
17
19
|
drydock (0.6.9)
|
18
20
|
json (2.7.2)
|
19
21
|
language_server-protocol (3.17.0.3)
|
22
|
+
logger (1.7.0)
|
20
23
|
method_source (1.0.0)
|
21
24
|
parallel (1.24.0)
|
22
25
|
parser (3.3.0.5)
|
23
26
|
ast (~> 2.4.1)
|
24
27
|
racc
|
28
|
+
prettier_print (1.2.1)
|
29
|
+
prism (1.4.0)
|
25
30
|
pry (0.14.2)
|
26
31
|
coderay (~> 1.1)
|
27
32
|
method_source (~> 1.0)
|
@@ -30,10 +35,29 @@ GEM
|
|
30
35
|
pry (>= 0.13, < 0.15)
|
31
36
|
public_suffix (6.0.1)
|
32
37
|
racc (1.7.3)
|
33
|
-
rack (
|
38
|
+
rack (3.1.16)
|
39
|
+
rack-parser (0.7.0)
|
40
|
+
rack
|
41
|
+
rack-test (2.2.0)
|
42
|
+
rack (>= 1.3)
|
34
43
|
rainbow (3.1.1)
|
44
|
+
rbs (3.9.4)
|
45
|
+
logger
|
35
46
|
regexp_parser (2.9.0)
|
36
47
|
rexml (3.3.7)
|
48
|
+
rspec (3.13.1)
|
49
|
+
rspec-core (~> 3.13.0)
|
50
|
+
rspec-expectations (~> 3.13.0)
|
51
|
+
rspec-mocks (~> 3.13.0)
|
52
|
+
rspec-core (3.13.5)
|
53
|
+
rspec-support (~> 3.13.0)
|
54
|
+
rspec-expectations (3.13.5)
|
55
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
56
|
+
rspec-support (~> 3.13.0)
|
57
|
+
rspec-mocks (3.13.5)
|
58
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
59
|
+
rspec-support (~> 3.13.0)
|
60
|
+
rspec-support (3.13.4)
|
37
61
|
rubocop (1.62.1)
|
38
62
|
json (~> 2.3)
|
39
63
|
language_server-protocol (>= 3.17.0)
|
@@ -47,8 +71,22 @@ GEM
|
|
47
71
|
unicode-display_width (>= 2.4.0, < 3.0)
|
48
72
|
rubocop-ast (1.31.2)
|
49
73
|
parser (>= 3.3.0.4)
|
74
|
+
rubocop-performance (1.23.1)
|
75
|
+
rubocop (>= 1.48.1, < 2.0)
|
76
|
+
rubocop-ast (>= 1.31.1, < 2.0)
|
77
|
+
rubocop-rspec (3.4.0)
|
78
|
+
rubocop (~> 1.61)
|
79
|
+
rubocop-thread_safety (0.6.0)
|
80
|
+
rubocop (>= 1.48.1)
|
81
|
+
ruby-lsp (0.26.0)
|
82
|
+
language_server-protocol (~> 3.17.0)
|
83
|
+
prism (>= 1.2, < 2.0)
|
84
|
+
rbs (>= 3, < 5)
|
50
85
|
ruby-progressbar (1.13.0)
|
86
|
+
stackprof (0.2.27)
|
51
87
|
storable (0.10.0)
|
88
|
+
syntax_tree (6.3.0)
|
89
|
+
prettier_print (>= 1.2.0)
|
52
90
|
sysinfo (0.10.0)
|
53
91
|
drydock (< 1.0)
|
54
92
|
storable (~> 0.10)
|
@@ -63,7 +101,15 @@ PLATFORMS
|
|
63
101
|
DEPENDENCIES
|
64
102
|
otto!
|
65
103
|
pry-byebug
|
104
|
+
rack-test
|
105
|
+
rspec (~> 3.12)
|
66
106
|
rubocop
|
107
|
+
rubocop-performance
|
108
|
+
rubocop-rspec
|
109
|
+
rubocop-thread_safety
|
110
|
+
ruby-lsp
|
111
|
+
stackprof
|
112
|
+
syntax_tree
|
67
113
|
tryouts
|
68
114
|
|
69
115
|
BUNDLED WITH
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -1,150 +1,99 @@
|
|
1
|
-
# Otto - 1.
|
1
|
+
# Otto - 1.2 (2025-08-18)
|
2
2
|
|
3
|
-
**
|
3
|
+
**Define your rack-apps in plain-text with built-in security.**
|
4
4
|
|
5
|
-
|
5
|
+

|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
A barebones app directory looks something like this:
|
7
|
+
Otto apps have three files: a rackup file, a Ruby class, and a routes file. The routes file is just plain text that maps URLs to Ruby methods.
|
10
8
|
|
11
9
|
```bash
|
12
|
-
|
13
|
-
|
14
|
-
config.ru app.rb routes
|
10
|
+
$ cd myapp && ls
|
11
|
+
config.ru app.rb routes
|
15
12
|
```
|
16
13
|
|
17
|
-
|
14
|
+
## Routes File
|
15
|
+
```
|
16
|
+
# routes
|
18
17
|
|
18
|
+
GET / App#index
|
19
|
+
POST /feedback App#receive_feedback
|
20
|
+
GET /product/:id App#show_product
|
21
|
+
GET /robots.txt App#robots_text
|
22
|
+
GET /404 App#not_found
|
23
|
+
```
|
19
24
|
|
20
|
-
|
25
|
+
## Ruby Class
|
26
|
+
```ruby
|
27
|
+
# app.rb
|
21
28
|
|
22
|
-
|
29
|
+
class App
|
30
|
+
def initialize(req, res)
|
31
|
+
@req, @res = req, res
|
32
|
+
end
|
23
33
|
|
24
|
-
|
25
|
-
|
26
|
-
|
34
|
+
def index
|
35
|
+
res.body = '<h1>Hello Otto</h1>'
|
36
|
+
end
|
27
37
|
|
28
|
-
|
38
|
+
def show_product
|
39
|
+
product_id = req.params[:id]
|
40
|
+
res.body = "Product: #{product_id}"
|
41
|
+
end
|
29
42
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
# You can also define these handlers when no
|
38
|
-
# route can be found or there's a server error. (optional)
|
39
|
-
GET /404 App#not_found
|
40
|
-
GET /500 App#server_error
|
43
|
+
def robots_text
|
44
|
+
res.header['Content-Type'] = "text/plain"
|
45
|
+
rules = 'User-agent: *', 'Disallow: /private/keep/out'
|
46
|
+
res.body = rules.join($/)
|
47
|
+
end
|
48
|
+
end
|
41
49
|
```
|
42
50
|
|
43
|
-
|
51
|
+
## Rackup File
|
52
|
+
```ruby
|
53
|
+
# config.ru
|
44
54
|
|
45
|
-
|
55
|
+
require 'otto'
|
56
|
+
require 'app'
|
46
57
|
|
47
|
-
|
48
|
-
class App
|
49
|
-
attr_reader :req, :res
|
50
|
-
|
51
|
-
# Otto creates an instance of this class for every request
|
52
|
-
# and passess the Rack::Request and Rack::Response objects.
|
53
|
-
def initialize req, res
|
54
|
-
@req, @res = req, res
|
55
|
-
end
|
56
|
-
|
57
|
-
def index
|
58
|
-
res.header['Content-Type'] = "text/html; charset=utf-8"
|
59
|
-
lines = [
|
60
|
-
'<img src="/img/otto.jpg" /><br/><br/>',
|
61
|
-
'Send feedback:<br/>',
|
62
|
-
'<form method="post"><input name="msg" /><input type="submit" /></form>',
|
63
|
-
'<a href="/product/100">A product example</a>'
|
64
|
-
]
|
65
|
-
res.body = lines.join($/)
|
66
|
-
end
|
67
|
-
|
68
|
-
def receive_feedback
|
69
|
-
res.body = req.params.inspect
|
70
|
-
end
|
71
|
-
|
72
|
-
def redirect
|
73
|
-
res.redirect '/robots.txt'
|
74
|
-
end
|
75
|
-
|
76
|
-
def robots_text
|
77
|
-
res.header['Content-Type'] = "text/plain"
|
78
|
-
rules = 'User-agent: *', 'Disallow: /private'
|
79
|
-
res.body = rules.join($/)
|
80
|
-
end
|
81
|
-
|
82
|
-
def display_product
|
83
|
-
res.header['Content-Type'] = "application/json; charset=utf-8"
|
84
|
-
prodid = req.params[:prodid]
|
85
|
-
res.body = '{"product":%s,"msg":"Hint: try another value"}' % [prodid]
|
86
|
-
end
|
87
|
-
|
88
|
-
def not_found
|
89
|
-
res.status = 404
|
90
|
-
res.body = "Item not found!"
|
91
|
-
end
|
92
|
-
|
93
|
-
def server_error
|
94
|
-
res.status = 500
|
95
|
-
res.body = "There was a server error!"
|
96
|
-
end
|
97
|
-
end
|
58
|
+
run Otto.new("./routes")
|
98
59
|
```
|
99
60
|
|
100
61
|
|
101
|
-
|
62
|
+
## Security Features
|
102
63
|
|
103
|
-
|
64
|
+
Otto includes optional security features for production apps:
|
104
65
|
|
105
66
|
```ruby
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
run app
|
113
|
-
}
|
67
|
+
# Enable security features
|
68
|
+
app = Otto.new("./routes", {
|
69
|
+
csrf_protection: true, # CSRF tokens and validation
|
70
|
+
request_validation: true, # Input sanitization and limits
|
71
|
+
trusted_proxies: ['10.0.0.0/8']
|
72
|
+
})
|
114
73
|
```
|
115
74
|
|
116
|
-
|
75
|
+
Security features include CSRF protection, input validation, security headers, and trusted proxy configuration.
|
117
76
|
|
77
|
+
## Requirements
|
118
78
|
|
119
|
-
|
79
|
+
- Ruby 3.4+
|
80
|
+
- Rack 3.1+
|
120
81
|
|
121
|
-
|
82
|
+
## Installation
|
122
83
|
|
123
84
|
```bash
|
124
|
-
|
125
|
-
|
126
|
-
[ Add it to yer Gemfile]
|
127
|
-
$ bundle install
|
128
|
-
|
129
|
-
$ git clone git://github.com/delano/otto.git
|
85
|
+
gem install otto
|
130
86
|
```
|
131
87
|
|
88
|
+
## AI Development Assistance
|
132
89
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
## More Information
|
137
|
-
|
138
|
-
* [Homepage](https://github.com/delano/otto)
|
139
|
-
|
140
|
-
|
141
|
-
## In the wild
|
142
|
-
|
143
|
-
Services that use Otto:
|
144
|
-
|
145
|
-
* [Onetime Secret](https://onetimesecret.com/) -- A safe way to share sensitive data.
|
90
|
+
Version 1.2.0's security features were developed with AI assistance:
|
146
91
|
|
92
|
+
* **Zed Agent (Claude Sonnet 4)** - Security implementation and testing
|
93
|
+
* **Claude Desktop** - Rack 3+ compatibility and debugging
|
94
|
+
* **GitHub Copilot** - Code completion
|
147
95
|
|
96
|
+
The maintainer remains responsible for all security decisions and implementation. We believe in transparency about development tools, especially for security-focused software.
|
148
97
|
|
149
98
|
## License
|
150
99
|
|
data/VERSION.yml
CHANGED
@@ -0,0 +1,78 @@
|
|
1
|
+
# examples/basic/app.rb (Streamlined with Design System)
|
2
|
+
|
3
|
+
require_relative '../../lib/otto/design_system'
|
4
|
+
|
5
|
+
class App
|
6
|
+
include Otto::DesignSystem
|
7
|
+
|
8
|
+
attr_reader :req, :res
|
9
|
+
|
10
|
+
def initialize(req, res)
|
11
|
+
@req = req
|
12
|
+
@res = res
|
13
|
+
res.headers['content-type'] = 'text/html; charset=utf-8'
|
14
|
+
end
|
15
|
+
|
16
|
+
def index
|
17
|
+
content = <<~HTML
|
18
|
+
<div class="otto-card otto-text-center">
|
19
|
+
<img src="/img/otto.jpg" alt="Otto Framework" class="otto-logo" />
|
20
|
+
<h1>Otto Framework</h1>
|
21
|
+
<p>Minimal Ruby web framework with style</p>
|
22
|
+
</div>
|
23
|
+
|
24
|
+
#{otto_card("Send Feedback") do
|
25
|
+
otto_form_wrapper do
|
26
|
+
otto_input("msg", placeholder: "Your message...") +
|
27
|
+
otto_button("Send Feedback")
|
28
|
+
end
|
29
|
+
end}
|
30
|
+
HTML
|
31
|
+
|
32
|
+
res.send_cookie :sess, 1_234_567, 3600
|
33
|
+
res.body = otto_page(content)
|
34
|
+
end
|
35
|
+
|
36
|
+
def receive_feedback
|
37
|
+
message = req.params['msg']&.strip
|
38
|
+
|
39
|
+
content = if message.nil? || message.empty?
|
40
|
+
otto_alert("error", "Empty Message", "Please enter a message before submitting.")
|
41
|
+
else
|
42
|
+
otto_alert("success", "Feedback Received", "Thanks for your message!") +
|
43
|
+
otto_card("Your Message") { otto_code_block(message, 'text') }
|
44
|
+
end
|
45
|
+
|
46
|
+
content += "<p>#{otto_link("← Back", "/")}</p>"
|
47
|
+
res.body = otto_page(content, "Feedback")
|
48
|
+
end
|
49
|
+
|
50
|
+
def redirect
|
51
|
+
res.redirect '/robots.txt'
|
52
|
+
end
|
53
|
+
|
54
|
+
def robots_text
|
55
|
+
res.headers['content-type'] = 'text/plain'
|
56
|
+
res.body = ['User-agent: *', 'Disallow: /private'].join($/)
|
57
|
+
end
|
58
|
+
|
59
|
+
def display_product
|
60
|
+
res.headers['content-type'] = 'application/json; charset=utf-8'
|
61
|
+
prodid = req.params[:prodid]
|
62
|
+
res.body = format('{"product":%s,"msg":"Hint: try another value"}', prodid)
|
63
|
+
end
|
64
|
+
|
65
|
+
def not_found
|
66
|
+
res.status = 404
|
67
|
+
content = otto_alert("error", "Not Found", "The requested page could not be found.")
|
68
|
+
content += "<p>#{otto_link("← Home", "/")}</p>"
|
69
|
+
res.body = otto_page(content, "404")
|
70
|
+
end
|
71
|
+
|
72
|
+
def server_error
|
73
|
+
res.status = 500
|
74
|
+
content = otto_alert("error", "Server Error", "An internal server error occurred.")
|
75
|
+
content += "<p>#{otto_link("← Home", "/")}</p>"
|
76
|
+
res.body = otto_page(content, "500")
|
77
|
+
end
|
78
|
+
end
|