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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +251 -0
- data/README.md +18 -0
- data/docs/ADAPTERS.md +478 -0
- data/docs/ARCHITECTURE.md +222 -0
- data/docs/CONTENT.md +967 -0
- data/docs/PERFORMANCE.md +492 -0
- data/docs/PLUGIN_DEVELOPMENT.md +688 -0
- data/docs/USAGE.md +4998 -0
- data/docs/plugins/API_KEY_AUTH.md +232 -0
- data/docs/plugins/BASIC_AUTH.md +582 -0
- data/docs/plugins/BEARER_AUTH.md +394 -0
- data/docs/plugins/CACHE.md +369 -0
- data/docs/plugins/COMPRESSION.md +216 -0
- data/docs/plugins/COOKIES.md +30 -0
- data/docs/plugins/CORS.md +283 -0
- data/docs/plugins/CSRF.md +751 -0
- data/docs/plugins/ETAG.md +308 -0
- data/docs/plugins/FORM_PARSER.md +193 -0
- data/docs/plugins/HEALTH_CHECK.md +469 -0
- data/docs/plugins/JSON.md +291 -0
- data/docs/plugins/MULTIPART.md +427 -0
- data/docs/plugins/RATE_LIMITER.md +368 -0
- data/docs/plugins/REQUEST_ID.md +369 -0
- data/docs/plugins/REQUEST_LOGGER.md +151 -0
- data/docs/plugins/SECURITY.md +193 -0
- data/docs/plugins/SESSION.md +98 -0
- data/lib/aris/adapters/rack/adapter.rb +17 -2
- data/lib/aris/adapters/rack/request.rb +29 -11
- data/lib/aris/plugins/basic_auth.rb +3 -1
- data/lib/aris/plugins/cookies.rb +4 -32
- data/lib/aris/plugins/cors.rb +8 -1
- data/lib/aris/plugins/csrf.rb +63 -22
- data/lib/aris/plugins/flash.rb +3 -1
- data/lib/aris/plugins/form_parser.rb +52 -31
- data/lib/aris/plugins/multipart.rb +22 -2
- data/lib/aris/plugins/request_logger.rb +8 -1
- data/lib/aris/plugins/security_headers.rb +8 -1
- data/lib/aris/plugins/session.rb +150 -99
- data/lib/aris/response_helpers.rb +41 -0
- data/lib/aris/version.rb +2 -2
- metadata +31 -3
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
|
|
2
|
+
# API Key Auth Plugin
|
|
3
|
+
|
|
4
|
+
Simple API key authentication via custom header.
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
```ruby
|
|
9
|
+
require_relative 'aris/plugins/api_key_auth'
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Basic Usage
|
|
13
|
+
|
|
14
|
+
### Single Key
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
auth = Aris::Plugins::ApiKeyAuth.build(key: ENV['API_KEY'])
|
|
18
|
+
|
|
19
|
+
Aris.routes({
|
|
20
|
+
"api.example.com": {
|
|
21
|
+
use: [auth],
|
|
22
|
+
"/data": { get: { to: DataHandler } }
|
|
23
|
+
}
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
**Request:**
|
|
28
|
+
```bash
|
|
29
|
+
curl -H "X-API-Key: your-secret-key" https://api.example.com/data
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Configuration
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
auth = Aris::Plugins::ApiKeyAuth.build(
|
|
38
|
+
key: 'secret-key', # Single key
|
|
39
|
+
keys: ['key1', 'key2'], # Multiple keys
|
|
40
|
+
validator: ->(k) { valid?(k) }, # Custom validation
|
|
41
|
+
header: 'X-API-Key', # Header name (default)
|
|
42
|
+
realm: 'API' # Realm for WWW-Authenticate
|
|
43
|
+
)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Options
|
|
47
|
+
|
|
48
|
+
| Option | Required | Description |
|
|
49
|
+
|:---|:---|:---|
|
|
50
|
+
| `key` | * | Single valid key |
|
|
51
|
+
| `keys` | * | Array of valid keys |
|
|
52
|
+
| `validator` | * | Custom validation `(key) -> Boolean` |
|
|
53
|
+
| `header` | No | Header name (default: `X-API-Key`) |
|
|
54
|
+
| `realm` | No | Realm (default: `API`) |
|
|
55
|
+
|
|
56
|
+
**Note:** Must provide `key`, `keys`, OR `validator`.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Common Patterns
|
|
61
|
+
|
|
62
|
+
### Multiple Valid Keys
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
auth = Aris::Plugins::ApiKeyAuth.build(
|
|
66
|
+
keys: [
|
|
67
|
+
ENV['PUBLIC_API_KEY'],
|
|
68
|
+
ENV['PARTNER_API_KEY'],
|
|
69
|
+
ENV['INTERNAL_API_KEY']
|
|
70
|
+
]
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Database Validation
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
auth = Aris::Plugins::ApiKeyAuth.build(
|
|
78
|
+
validator: ->(key) {
|
|
79
|
+
ApiKey.exists?(key: key, active: true)
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Redis-Cached Validation
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
auth = Aris::Plugins::ApiKeyAuth.build(
|
|
88
|
+
validator: ->(key) {
|
|
89
|
+
Rails.cache.fetch("api_key:#{key}", expires_in: 5.minutes) do
|
|
90
|
+
ApiKey.valid?(key)
|
|
91
|
+
end
|
|
92
|
+
}
|
|
93
|
+
)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Custom Header
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
auth = Aris::Plugins::ApiKeyAuth.build(
|
|
100
|
+
key: ENV['API_KEY'],
|
|
101
|
+
header: 'X-Custom-API-Key'
|
|
102
|
+
)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Request:**
|
|
106
|
+
```bash
|
|
107
|
+
curl -H "X-Custom-API-Key: secret" https://api.example.com/data
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Per-Domain Keys
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
public_auth = Aris::Plugins::ApiKeyAuth.build(key: ENV['PUBLIC_KEY'])
|
|
114
|
+
admin_auth = Aris::Plugins::ApiKeyAuth.build(key: ENV['ADMIN_KEY'])
|
|
115
|
+
|
|
116
|
+
Aris.routes({
|
|
117
|
+
"api.example.com": {
|
|
118
|
+
use: [public_auth],
|
|
119
|
+
"/data": { get: { to: DataHandler } }
|
|
120
|
+
},
|
|
121
|
+
"admin-api.example.com": {
|
|
122
|
+
use: [admin_auth],
|
|
123
|
+
"/admin": { get: { to: AdminHandler } }
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Accessing Key in Handler
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
class DataHandler
|
|
134
|
+
def self.call(request, params)
|
|
135
|
+
api_key = request.instance_variable_get(:@api_key)
|
|
136
|
+
|
|
137
|
+
# Look up associated user/account
|
|
138
|
+
account = Account.find_by(api_key: api_key)
|
|
139
|
+
|
|
140
|
+
{ data: account.data }
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Production Tips
|
|
148
|
+
|
|
149
|
+
**1. Use Environment Variables**
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
auth = Aris::Plugins::ApiKeyAuth.build(
|
|
153
|
+
key: ENV.fetch('API_SECRET_KEY')
|
|
154
|
+
)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**2. Rotate Keys Regularly**
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
# Support old + new during rotation
|
|
161
|
+
auth = Aris::Plugins::ApiKeyAuth.build(
|
|
162
|
+
keys: [ENV['API_KEY_CURRENT'], ENV['API_KEY_PREVIOUS']]
|
|
163
|
+
)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**3. Rate Limit by Key**
|
|
167
|
+
|
|
168
|
+
```ruby
|
|
169
|
+
rate_limit = RateLimiter.build(
|
|
170
|
+
key_extractor: ->(request) {
|
|
171
|
+
request.instance_variable_get(:@api_key)
|
|
172
|
+
}
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
Aris.routes({
|
|
176
|
+
"api.example.com": {
|
|
177
|
+
use: [api_key_auth, rate_limit], # Auth first, then rate limit by key
|
|
178
|
+
"/data": { get: { to: DataHandler } }
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**4. Log Failed Attempts**
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
auth = Aris::Plugins::ApiKeyAuth.build(
|
|
187
|
+
validator: ->(key) {
|
|
188
|
+
valid = ApiKey.valid?(key)
|
|
189
|
+
unless valid
|
|
190
|
+
Rails.logger.warn("Invalid API key attempt: #{key[0..8]}...")
|
|
191
|
+
end
|
|
192
|
+
valid
|
|
193
|
+
}
|
|
194
|
+
)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Error Response
|
|
200
|
+
|
|
201
|
+
**401 Unauthorized:**
|
|
202
|
+
```json
|
|
203
|
+
{
|
|
204
|
+
"error": "Unauthorized",
|
|
205
|
+
"message": "Invalid API key"
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
**Headers:**
|
|
210
|
+
```
|
|
211
|
+
HTTP/1.1 401 Unauthorized
|
|
212
|
+
content-type: application/json
|
|
213
|
+
WWW-Authenticate: ApiKey realm="API"
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Security Notes
|
|
219
|
+
|
|
220
|
+
- ✅ Use HTTPS always (keys sent in plain text)
|
|
221
|
+
- ✅ Store keys hashed in database
|
|
222
|
+
- ✅ Rotate keys regularly
|
|
223
|
+
- ✅ Different keys per client/environment
|
|
224
|
+
- ✅ Combine with rate limiting
|
|
225
|
+
- ❌ Never log full keys
|
|
226
|
+
- ❌ Never commit keys to version control
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
Need help? Check out the [full plugin development guide](../docs/plugin-development.md).
|