aris 1.4.2 → 1.5.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.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +251 -0
  3. data/README.md +18 -0
  4. data/docs/ADAPTERS.md +478 -0
  5. data/docs/ARCHITECTURE.md +222 -0
  6. data/docs/CONTENT.md +967 -0
  7. data/docs/PERFORMANCE.md +492 -0
  8. data/docs/PLUGIN_DEVELOPMENT.md +688 -0
  9. data/docs/USAGE.md +4998 -0
  10. data/docs/plugins/API_KEY_AUTH.md +232 -0
  11. data/docs/plugins/BASIC_AUTH.md +582 -0
  12. data/docs/plugins/BEARER_AUTH.md +394 -0
  13. data/docs/plugins/CACHE.md +369 -0
  14. data/docs/plugins/COMPRESSION.md +216 -0
  15. data/docs/plugins/COOKIES.md +30 -0
  16. data/docs/plugins/CORS.md +283 -0
  17. data/docs/plugins/CSRF.md +751 -0
  18. data/docs/plugins/ETAG.md +308 -0
  19. data/docs/plugins/FORM_PARSER.md +193 -0
  20. data/docs/plugins/HEALTH_CHECK.md +469 -0
  21. data/docs/plugins/JSON.md +291 -0
  22. data/docs/plugins/MULTIPART.md +427 -0
  23. data/docs/plugins/RATE_LIMITER.md +368 -0
  24. data/docs/plugins/REQUEST_ID.md +369 -0
  25. data/docs/plugins/REQUEST_LOGGER.md +151 -0
  26. data/docs/plugins/SECURITY.md +193 -0
  27. data/docs/plugins/SESSION.md +98 -0
  28. data/lib/aris/adapters/rack/adapter.rb +17 -2
  29. data/lib/aris/adapters/rack/request.rb +29 -11
  30. data/lib/aris/plugins/basic_auth.rb +3 -1
  31. data/lib/aris/plugins/cookies.rb +4 -32
  32. data/lib/aris/plugins/cors.rb +8 -1
  33. data/lib/aris/plugins/csrf.rb +63 -22
  34. data/lib/aris/plugins/flash.rb +3 -1
  35. data/lib/aris/plugins/form_parser.rb +52 -31
  36. data/lib/aris/plugins/multipart.rb +22 -2
  37. data/lib/aris/plugins/request_logger.rb +8 -1
  38. data/lib/aris/plugins/security_headers.rb +8 -1
  39. data/lib/aris/plugins/session.rb +150 -99
  40. data/lib/aris/response_helpers.rb +41 -0
  41. data/lib/aris/version.rb +2 -2
  42. metadata +31 -3
@@ -0,0 +1,283 @@
1
+ # CORS Plugin
2
+
3
+ Enable Cross-Origin Resource Sharing (CORS) for APIs accessed from web browsers on different domains.
4
+
5
+ ## Installation
6
+
7
+ ```ruby
8
+ # lib/aris.rb already includes this
9
+ require_relative 'aris/plugins/cors'
10
+ ```
11
+
12
+ ## Basic Usage
13
+
14
+ ### Allow All Origins
15
+
16
+ ```ruby
17
+ cors = Aris::Plugins::Cors.build(origins: '*')
18
+
19
+ Aris.routes({
20
+ "api.example.com": {
21
+ use: [cors],
22
+ "/data": { get: { to: DataHandler } }
23
+ }
24
+ })
25
+ ```
26
+
27
+ **Headers set:**
28
+ ```
29
+ Access-Control-Allow-Origin: *
30
+ Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
31
+ Access-Control-Allow-Headers: content-type, Authorization
32
+ Access-Control-Max-Age: 86400
33
+ ```
34
+
35
+ ### Specific Origins
36
+
37
+ ```ruby
38
+ cors = Aris::Plugins::Cors.build(
39
+ origins: [
40
+ 'https://app.example.com',
41
+ 'https://admin.example.com'
42
+ ]
43
+ )
44
+ ```
45
+
46
+ Only requests from these origins get CORS headers. Others are blocked by the browser.
47
+
48
+ ---
49
+
50
+ ## Configuration
51
+
52
+ ```ruby
53
+ cors = Aris::Plugins::Cors.build(
54
+ origins: ['https://app.example.com'], # Array or '*'
55
+ methods: ['GET', 'POST', 'PUT', 'DELETE'],
56
+ headers: ['content-type', 'Authorization', 'X-Custom'],
57
+ credentials: true,
58
+ max_age: 3600, # Cache preflight for 1 hour
59
+ expose_headers: ['X-Total-Count', 'X-Page']
60
+ )
61
+ ```
62
+
63
+ | Option | Default | Description |
64
+ |:---|:---|:---|
65
+ | `origins` | `'*'` | Allowed origins (array or wildcard) |
66
+ | `methods` | All common methods | HTTP methods to allow |
67
+ | `headers` | content-type, Authorization | Headers clients can send |
68
+ | `credentials` | `false` | Allow cookies/auth headers |
69
+ | `max_age` | `86400` | Preflight cache time (seconds) |
70
+ | `expose_headers` | `[]` | Headers clients can read |
71
+
72
+ ---
73
+
74
+ ## Common Patterns
75
+
76
+ ### Frontend + API on Different Domains
77
+
78
+ ```ruby
79
+ # API on api.example.com
80
+ # Frontend on app.example.com
81
+
82
+ cors = Aris::Plugins::Cors.build(
83
+ origins: ['https://app.example.com'],
84
+ credentials: true # Allow cookies
85
+ )
86
+
87
+ Aris.routes({
88
+ "api.example.com": {
89
+ use: [cors],
90
+ "/users": { get: { to: UsersHandler } }
91
+ }
92
+ })
93
+ ```
94
+
95
+ **Frontend (React/Vue):**
96
+ ```javascript
97
+ fetch('https://api.example.com/users', {
98
+ credentials: 'include' // Send cookies
99
+ })
100
+ ```
101
+
102
+ ### Multiple Environments
103
+
104
+ ```ruby
105
+ origins = case ENV['RACK_ENV']
106
+ when 'production'
107
+ ['https://app.example.com']
108
+ when 'staging'
109
+ ['https://staging.example.com']
110
+ when 'development'
111
+ ['http://localhost:3000', 'http://localhost:8080']
112
+ end
113
+
114
+ cors = Aris::Plugins::Cors.build(origins: origins)
115
+ ```
116
+
117
+ ### Public API + Private Admin API
118
+
119
+ ```ruby
120
+ public_cors = Aris::Plugins::Cors.build(origins: '*')
121
+
122
+ admin_cors = Aris::Plugins::Cors.build(
123
+ origins: ['https://admin.example.com'],
124
+ credentials: true
125
+ )
126
+
127
+ Aris.routes({
128
+ "api.example.com": {
129
+ use: [public_cors],
130
+ "/public": { get: { to: PublicHandler } }
131
+ },
132
+ "admin-api.example.com": {
133
+ use: [admin_cors],
134
+ "/admin": { get: { to: AdminHandler } }
135
+ }
136
+ })
137
+ ```
138
+
139
+ ---
140
+
141
+ ## How CORS Works
142
+
143
+ **Simple Request (GET, POST with simple headers):**
144
+ 1. Browser sends request with `Origin` header
145
+ 2. Server responds with `Access-Control-Allow-Origin`
146
+ 3. Browser allows response if origin matches
147
+
148
+ **Preflight Request (PUT, DELETE, custom headers):**
149
+ 1. Browser sends OPTIONS request first
150
+ 2. Server responds with allowed methods/headers
151
+ 3. Browser sends actual request if allowed
152
+ 4. CORS plugin automatically handles OPTIONS with 204 response
153
+
154
+ ---
155
+
156
+ ## Troubleshooting
157
+
158
+ **"CORS error" in browser console:**
159
+
160
+ Check origin is in allowed list:
161
+ ```ruby
162
+ cors = Aris::Plugins::Cors.build(
163
+ origins: ['https://app.example.com'] # Must match exactly
164
+ )
165
+ ```
166
+
167
+ **Credentials not working:**
168
+
169
+ Enable both in plugin and frontend:
170
+ ```ruby
171
+ cors = Aris::Plugins::Cors.build(
172
+ origins: ['https://app.example.com'], # Can't use '*' with credentials
173
+ credentials: true
174
+ )
175
+ ```
176
+
177
+ ```javascript
178
+ fetch(url, { credentials: 'include' })
179
+ ```
180
+
181
+ **Custom headers blocked:**
182
+
183
+ Add to allowed headers:
184
+ ```ruby
185
+ cors = Aris::Plugins::Cors.build(
186
+ headers: ['content-type', 'Authorization', 'X-My-Custom-Header']
187
+ )
188
+ ```
189
+
190
+ ---
191
+
192
+ ## Production Tips
193
+
194
+ **1. Be Specific in Production**
195
+
196
+ ```ruby
197
+ # ❌ Too permissive
198
+ cors = Aris::Plugins::Cors.build(origins: '*')
199
+
200
+ # ✅ Explicit origins
201
+ cors = Aris::Plugins::Cors.build(
202
+ origins: [
203
+ 'https://app.example.com',
204
+ 'https://www.example.com'
205
+ ]
206
+ )
207
+ ```
208
+
209
+ **2. Use Environment Variables**
210
+
211
+ ```ruby
212
+ cors = Aris::Plugins::Cors.build(
213
+ origins: ENV['CORS_ORIGINS'].split(',')
214
+ )
215
+ ```
216
+
217
+ **3. Different CORS for Different Routes**
218
+
219
+ ```ruby
220
+ Aris.routes({
221
+ "api.example.com": {
222
+ "/public": {
223
+ use: [public_cors],
224
+ "/posts": { get: { to: PostsHandler } }
225
+ },
226
+ "/admin": {
227
+ use: [admin_cors],
228
+ "/users": { delete: { to: DeleteUserHandler } }
229
+ }
230
+ }
231
+ })
232
+ ```
233
+
234
+ ---
235
+
236
+ ## Important: OPTIONS Routes
237
+
238
+ CORS preflight requires OPTIONS to be defined:
239
+
240
+ ```ruby
241
+ Aris.routes({
242
+ "api.example.com": {
243
+ use: [cors],
244
+ "/users": {
245
+ get: { to: UsersHandler },
246
+ post: { to: CreateUserHandler },
247
+ options: { to: UsersHandler } # Required for preflight
248
+ }
249
+ }
250
+ })
251
+ Or use a catch-all handler:
252
+ rubyclass OptionsHandler
253
+ def self.call(request, params)
254
+ # CORS plugin handles the response
255
+ nil
256
+ end
257
+ end
258
+ ```
259
+
260
+ This is a common pattern in web frameworks - the route must exist for middleware to run.
261
+
262
+ ----
263
+
264
+ ## Security Notes
265
+
266
+ - ✅ CORS prevents malicious sites from making requests on behalf of users
267
+ - ✅ Always use specific origins in production (not `'*'`)
268
+ - ✅ Only enable `credentials: true` when necessary
269
+ - ✅ Combine with CSRF protection for state-changing requests
270
+ - ❌ CORS alone doesn't prevent XSS or authentication bypass
271
+ - ❌ CORS is browser-enforced (curl/Postman ignore it)
272
+
273
+ ---
274
+
275
+ Need help? Check out the [full plugin development guide](../docs/plugin-development.md).
276
+ ```
277
+
278
+ **Add to `lib/aris.rb`:**
279
+ ```ruby
280
+ require_relative 'aris/plugins/cors'
281
+ ```
282
+
283
+ All tests passing! 🎉