araxys 0.1.0__tar.gz
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.
- araxys-0.1.0/PKG-INFO +385 -0
- araxys-0.1.0/README.md +348 -0
- araxys-0.1.0/pyproject.toml +62 -0
- araxys-0.1.0/src/araxys/__init__.py +64 -0
- araxys-0.1.0/src/araxys/api_keys/__init__.py +1 -0
- araxys-0.1.0/src/araxys/api_keys/dependencies.py +71 -0
- araxys-0.1.0/src/araxys/api_keys/manager.py +194 -0
- araxys-0.1.0/src/araxys/api_keys/models.py +52 -0
- araxys-0.1.0/src/araxys/api_keys/storage.py +67 -0
- araxys-0.1.0/src/araxys/audit/__init__.py +1 -0
- araxys-0.1.0/src/araxys/audit/encryption.py +130 -0
- araxys-0.1.0/src/araxys/audit/events.py +9 -0
- araxys-0.1.0/src/araxys/audit/logger.py +114 -0
- araxys-0.1.0/src/araxys/core/__init__.py +1 -0
- araxys-0.1.0/src/araxys/core/config.py +167 -0
- araxys-0.1.0/src/araxys/core/exceptions.py +87 -0
- araxys-0.1.0/src/araxys/core/types.py +62 -0
- araxys-0.1.0/src/araxys/headers/__init__.py +1 -0
- araxys-0.1.0/src/araxys/headers/middleware.py +88 -0
- araxys-0.1.0/src/araxys/honeypot/__init__.py +1 -0
- araxys-0.1.0/src/araxys/honeypot/middleware.py +58 -0
- araxys-0.1.0/src/araxys/honeypot/trap.py +104 -0
- araxys-0.1.0/src/araxys/jwt_auth/__init__.py +1 -0
- araxys-0.1.0/src/araxys/jwt_auth/dependencies.py +92 -0
- araxys-0.1.0/src/araxys/jwt_auth/storage.py +76 -0
- araxys-0.1.0/src/araxys/jwt_auth/tokens.py +250 -0
- araxys-0.1.0/src/araxys/py.typed +0 -0
- araxys-0.1.0/src/araxys/rate_limit/__init__.py +1 -0
- araxys-0.1.0/src/araxys/rate_limit/backends.py +180 -0
- araxys-0.1.0/src/araxys/rate_limit/limiter.py +99 -0
- araxys-0.1.0/src/araxys/rate_limit/middleware.py +91 -0
- araxys-0.1.0/src/araxys/sanitize/__init__.py +1 -0
- araxys-0.1.0/src/araxys/sanitize/filters.py +165 -0
- araxys-0.1.0/src/araxys/sanitize/middleware.py +97 -0
- araxys-0.1.0/src/araxys/sanitize/patterns.py +215 -0
- araxys-0.1.0/src/araxys/shield.py +186 -0
araxys-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: araxys
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Plug & play security library for FastAPI — rate limiting, honeypots, JWT, API keys, and more.
|
|
5
|
+
Keywords: fastapi,security,rate-limiting,jwt,middleware,honeypot
|
|
6
|
+
Author: Andres Felipe Ramirez Muñoz
|
|
7
|
+
Author-email: Andres Felipe Ramirez Muñoz <andres.ramirez@porcicarnes.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Framework :: FastAPI
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Topic :: Security
|
|
13
|
+
Classifier: Typing :: Typed
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Requires-Dist: fastapi>=0.115.0
|
|
16
|
+
Requires-Dist: pydantic>=2.0
|
|
17
|
+
Requires-Dist: pydantic-settings>=2.0
|
|
18
|
+
Requires-Dist: pyjwt[crypto]>=2.8
|
|
19
|
+
Requires-Dist: bleach>=6.0
|
|
20
|
+
Requires-Dist: cryptography>=44.0
|
|
21
|
+
Requires-Dist: structlog>=24.0
|
|
22
|
+
Requires-Dist: araxys[redis] ; extra == 'all'
|
|
23
|
+
Requires-Dist: pytest>=8.0 ; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
|
|
25
|
+
Requires-Dist: httpx>=0.27 ; extra == 'dev'
|
|
26
|
+
Requires-Dist: fakeredis>=2.21 ; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff>=0.11 ; extra == 'dev'
|
|
28
|
+
Requires-Dist: mypy>=1.10 ; extra == 'dev'
|
|
29
|
+
Requires-Dist: pre-commit>=4.0 ; extra == 'dev'
|
|
30
|
+
Requires-Dist: uvicorn>=0.30 ; extra == 'dev'
|
|
31
|
+
Requires-Dist: redis>=5.0 ; extra == 'redis'
|
|
32
|
+
Requires-Python: >=3.13
|
|
33
|
+
Provides-Extra: all
|
|
34
|
+
Provides-Extra: dev
|
|
35
|
+
Provides-Extra: redis
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
<p align="center">
|
|
39
|
+
<img src="assets/araxyslogo.png" alt="Araxys Logo" width="400">
|
|
40
|
+
</p>
|
|
41
|
+
|
|
42
|
+
<p align="center">
|
|
43
|
+
<strong>Plug & Play Security for FastAPI</strong><br>
|
|
44
|
+
<em>Rate limiting · Honeypots · JWT · API Keys · Encrypted Audit Logging</em>
|
|
45
|
+
</p>
|
|
46
|
+
|
|
47
|
+
<p align="center">
|
|
48
|
+
<img src="https://img.shields.io/badge/python-3.13+-3776AB?style=for-the-badge&logo=python&logoColor=white" alt="Python">
|
|
49
|
+
<img src="https://img.shields.io/badge/FastAPI-0.115+-009688?style=for-the-badge&logo=fastapi&logoColor=white" alt="FastAPI">
|
|
50
|
+
<img src="https://img.shields.io/badge/Pydantic-v2-E92063?style=for-the-badge&logo=pydantic&logoColor=white" alt="Pydantic">
|
|
51
|
+
<img src="https://img.shields.io/badge/uv-package%20manager-DE5FE9?style=for-the-badge&logo=uv&logoColor=white" alt="uv">
|
|
52
|
+
<img src="https://img.shields.io/badge/license-MIT-green?style=for-the-badge" alt="License">
|
|
53
|
+
</p>
|
|
54
|
+
|
|
55
|
+
<p align="center">
|
|
56
|
+
<img src="https://img.shields.io/badge/encryption-AES--256--GCM-DC143C?style=flat-square" alt="AES-256-GCM">
|
|
57
|
+
<img src="https://img.shields.io/badge/JWT-OAuth2%20compliant-000000?style=flat-square&logo=jsonwebtokens&logoColor=white" alt="JWT">
|
|
58
|
+
<img src="https://img.shields.io/badge/Redis-optional-DC382D?style=flat-square&logo=redis&logoColor=white" alt="Redis">
|
|
59
|
+
<img src="https://img.shields.io/badge/structlog-logging-4B8BBE?style=flat-square" alt="structlog">
|
|
60
|
+
<img src="https://img.shields.io/badge/tests-63%20passed-brightgreen?style=flat-square" alt="Tests">
|
|
61
|
+
</p>
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## ⚡ What is Araxys?
|
|
66
|
+
|
|
67
|
+
**Araxys** is a comprehensive security library for [FastAPI](https://fastapi.tiangolo.com/) that provides enterprise-grade protection with a plug & play architecture. Add security to your API with **three lines of code** — no rewrites, no boilerplate.
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from fastapi import FastAPI
|
|
71
|
+
from araxys import AraxysShield, AraxysConfig
|
|
72
|
+
|
|
73
|
+
app = FastAPI()
|
|
74
|
+
shield = AraxysShield(app, AraxysConfig(secret_key="your-32-char-secret-key-here!!!!"))
|
|
75
|
+
# That's it. Your API is now protected. 🛡️
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 🧩 Modules
|
|
81
|
+
|
|
82
|
+
| Module | Description | Status |
|
|
83
|
+
|--------|------------|--------|
|
|
84
|
+
| 🚦 **Rate Limiting** | Dynamic sliding window with escalating bans | ✅ Ready |
|
|
85
|
+
| 🍯 **Honeypots** | Fake endpoints that auto-ban bots | ✅ Ready |
|
|
86
|
+
| 🔑 **API Keys** | Scoped keys with SHA-256 hashing & expiration | ✅ Ready |
|
|
87
|
+
| 🎟️ **JWT Auth** | Access + Refresh tokens with rotation & revocation | ✅ Ready |
|
|
88
|
+
| 🛡️ **Secure Headers** | HSTS, CSP, X-Frame-Options & more (OWASP) | ✅ Ready |
|
|
89
|
+
| 🧹 **Sanitization** | SQLi detection & XSS stripping | ✅ Ready |
|
|
90
|
+
| 📋 **Audit Logging** | AES-256-GCM encrypted structured logs | ✅ Ready |
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 📦 Installation
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# Core (in-memory backends)
|
|
98
|
+
pip install araxys
|
|
99
|
+
|
|
100
|
+
# With Redis support (recommended for production)
|
|
101
|
+
pip install araxys[redis]
|
|
102
|
+
|
|
103
|
+
# Development
|
|
104
|
+
pip install araxys[dev]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
> **Requires Python 3.13+**
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 🚀 Quick Start
|
|
112
|
+
|
|
113
|
+
### Full Protection (All Modules)
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from fastapi import FastAPI
|
|
117
|
+
from araxys import AraxysShield, AraxysConfig
|
|
118
|
+
|
|
119
|
+
app = FastAPI()
|
|
120
|
+
|
|
121
|
+
shield = AraxysShield(
|
|
122
|
+
app,
|
|
123
|
+
AraxysConfig(
|
|
124
|
+
secret_key="super-secret-key-at-least-32-chars!",
|
|
125
|
+
redis_url="redis://localhost:6379", # Optional — omit for in-memory
|
|
126
|
+
rate_limit={"window_seconds": 60, "max_requests": 100},
|
|
127
|
+
honeypot={"paths": ["/admin/config", "/wp-admin", "/.env"]},
|
|
128
|
+
secure_headers=True,
|
|
129
|
+
sanitize=True,
|
|
130
|
+
audit={"encrypt": True, "log_file": "audit.log"},
|
|
131
|
+
),
|
|
132
|
+
)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### API Key Authentication
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from fastapi import Depends
|
|
139
|
+
from araxys import Scope
|
|
140
|
+
from araxys.api_keys.dependencies import require_api_key
|
|
141
|
+
from araxys.api_keys.models import APIKeyRecord
|
|
142
|
+
|
|
143
|
+
# Create a key
|
|
144
|
+
key = await shield.api_key_manager.create_key(
|
|
145
|
+
owner="service-a",
|
|
146
|
+
scopes=[Scope.READ, Scope.WRITE],
|
|
147
|
+
ttl_days=90,
|
|
148
|
+
)
|
|
149
|
+
print(f"Save this key: {key.raw_key}") # Shown only once!
|
|
150
|
+
|
|
151
|
+
# Protect an endpoint
|
|
152
|
+
@app.get("/data")
|
|
153
|
+
async def get_data(
|
|
154
|
+
key: APIKeyRecord = Depends(
|
|
155
|
+
require_api_key(Scope.READ, manager=shield.api_key_manager)
|
|
156
|
+
),
|
|
157
|
+
):
|
|
158
|
+
return {"data": "protected", "owner": key.owner}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### JWT with Token Rotation
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
from fastapi import Depends
|
|
165
|
+
from araxys import Scope
|
|
166
|
+
from araxys.jwt_auth.dependencies import require_jwt
|
|
167
|
+
from araxys.jwt_auth.tokens import TokenPayload
|
|
168
|
+
|
|
169
|
+
# Login — issue tokens
|
|
170
|
+
@app.post("/auth/login")
|
|
171
|
+
async def login(username: str, password: str):
|
|
172
|
+
# ... validate credentials ...
|
|
173
|
+
pair = await shield.jwt_manager.create_token_pair(
|
|
174
|
+
subject=user.id,
|
|
175
|
+
scopes=[Scope.READ, Scope.WRITE],
|
|
176
|
+
)
|
|
177
|
+
return pair.model_dump()
|
|
178
|
+
|
|
179
|
+
# Refresh — rotate tokens (old refresh token is blacklisted)
|
|
180
|
+
@app.post("/auth/refresh")
|
|
181
|
+
async def refresh(refresh_token: str):
|
|
182
|
+
new_pair = await shield.jwt_manager.rotate_tokens(refresh_token)
|
|
183
|
+
return new_pair.model_dump()
|
|
184
|
+
|
|
185
|
+
# Protected endpoint
|
|
186
|
+
@app.get("/profile")
|
|
187
|
+
async def profile(
|
|
188
|
+
user: TokenPayload = Depends(
|
|
189
|
+
require_jwt(Scope.READ, jwt_manager=shield.jwt_manager)
|
|
190
|
+
),
|
|
191
|
+
):
|
|
192
|
+
return {"user_id": user.sub, "scopes": user.scopes}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## 🏗️ Architecture
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
src/araxys/
|
|
201
|
+
├── core/ # Config, exceptions, shared types
|
|
202
|
+
│ ├── config.py # Pydantic Settings (env var support)
|
|
203
|
+
│ ├── exceptions.py # Custom exception hierarchy
|
|
204
|
+
│ └── types.py # Scope, AuditEntry, SecurityContext
|
|
205
|
+
├── rate_limit/ # 🚦 Dynamic rate limiting
|
|
206
|
+
│ ├── backends.py # Protocol + InMemory + Redis
|
|
207
|
+
│ ├── limiter.py # Sliding window + escalation
|
|
208
|
+
│ └── middleware.py # ASGI middleware
|
|
209
|
+
├── honeypot/ # 🍯 Trap endpoints
|
|
210
|
+
│ ├── trap.py # Route registration + auto-ban
|
|
211
|
+
│ └── middleware.py # IP ban enforcement
|
|
212
|
+
├── api_keys/ # 🔑 API Key management
|
|
213
|
+
│ ├── models.py # Pydantic models
|
|
214
|
+
│ ├── manager.py # CRUD + verification
|
|
215
|
+
│ ├── storage.py # Protocol + InMemory
|
|
216
|
+
│ └── dependencies.py # FastAPI dependencies
|
|
217
|
+
├── jwt_auth/ # 🎟️ JWT tokens
|
|
218
|
+
│ ├── tokens.py # Create, decode, rotate
|
|
219
|
+
│ ├── storage.py # JTI blacklisting
|
|
220
|
+
│ └── dependencies.py # FastAPI dependencies
|
|
221
|
+
├── headers/ # 🛡️ Security headers
|
|
222
|
+
│ └── middleware.py # HSTS, CSP, X-Frame-Options
|
|
223
|
+
├── sanitize/ # 🧹 Input sanitization
|
|
224
|
+
│ ├── patterns.py # SQLi + XSS regex patterns
|
|
225
|
+
│ ├── filters.py # Detection + stripping
|
|
226
|
+
│ └── middleware.py # ASGI middleware
|
|
227
|
+
├── audit/ # 📋 Audit logging
|
|
228
|
+
│ ├── encryption.py # AES-256-GCM + PBKDF2
|
|
229
|
+
│ ├── logger.py # Structured logger
|
|
230
|
+
│ └── events.py # Event types
|
|
231
|
+
└── shield.py # ⚡ Main orchestrator
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## 🔐 Security Features in Detail
|
|
237
|
+
|
|
238
|
+
### Rate Limiting
|
|
239
|
+
|
|
240
|
+
- **Sliding window** counter per IP + endpoint
|
|
241
|
+
- **Escalating bans**: repeated violations increase ban duration exponentially
|
|
242
|
+
- **X-RateLimit headers**: `Limit`, `Remaining`, `Window` injected in every response
|
|
243
|
+
- **Path exclusion**: Skip `/docs`, `/healthz`, etc.
|
|
244
|
+
|
|
245
|
+
### Honeypot Endpoints
|
|
246
|
+
|
|
247
|
+
- Registers **fake routes** like `/admin/config`, `/wp-admin`, `/.env`
|
|
248
|
+
- Returns **200 OK** with fake content (doesn't alert the bot)
|
|
249
|
+
- **Auto-bans the IP** across ALL endpoints
|
|
250
|
+
- Integrates with audit logging
|
|
251
|
+
|
|
252
|
+
### API Key Management
|
|
253
|
+
|
|
254
|
+
- **256-bit entropy** keys via `secrets.token_urlsafe`
|
|
255
|
+
- Stored as **SHA-256 hashes** (raw key is never persisted)
|
|
256
|
+
- **Scope-based authorization**: `read`, `write`, `admin`
|
|
257
|
+
- **Expiration support** with configurable TTL
|
|
258
|
+
- **Pluggable storage**: implement the `APIKeyStorage` protocol with your database
|
|
259
|
+
|
|
260
|
+
### JWT with Token Rotation
|
|
261
|
+
|
|
262
|
+
- **Access + Refresh** token pairs following OAuth2 best practices
|
|
263
|
+
- **JTI-based revocation**: each refresh token has a unique ID
|
|
264
|
+
- **Replay attack detection**: reusing a rotated refresh token triggers an alert
|
|
265
|
+
- **Configurable TTLs**: access (default 30min), refresh (default 7 days)
|
|
266
|
+
- **Scope embedding** in token claims
|
|
267
|
+
|
|
268
|
+
### Secure Headers
|
|
269
|
+
|
|
270
|
+
| Header | Default Value |
|
|
271
|
+
|--------|--------------|
|
|
272
|
+
| `Strict-Transport-Security` | `max-age=31536000; includeSubDomains` |
|
|
273
|
+
| `X-Content-Type-Options` | `nosniff` |
|
|
274
|
+
| `X-Frame-Options` | `DENY` |
|
|
275
|
+
| `X-XSS-Protection` | `0` (disabled — modern best practice) |
|
|
276
|
+
| `Referrer-Policy` | `strict-origin-when-cross-origin` |
|
|
277
|
+
| `Content-Security-Policy` | Configurable |
|
|
278
|
+
| `Permissions-Policy` | Configurable |
|
|
279
|
+
|
|
280
|
+
### Payload Sanitization
|
|
281
|
+
|
|
282
|
+
- **16 SQL injection patterns**: UNION, DROP, blind injection, time-based, etc.
|
|
283
|
+
- **9 XSS patterns**: script tags, JS URIs, event handlers, iframes
|
|
284
|
+
- **Recursive scanning** with configurable depth limit
|
|
285
|
+
- SQLi → **block** (400 response) · XSS → **strip** (bleach)
|
|
286
|
+
|
|
287
|
+
### Encrypted Audit Logging
|
|
288
|
+
|
|
289
|
+
- **AES-256-GCM** authenticated encryption (confidentiality + integrity)
|
|
290
|
+
- **PBKDF2-HMAC-SHA256** key derivation (480,000 iterations — OWASP 2023)
|
|
291
|
+
- **Per-entry unique salt + nonce** (no two entries share the same key material)
|
|
292
|
+
- **Tamper detection**: GCM authentication tag catches any modification
|
|
293
|
+
- Structured output via **structlog**
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## ⚙️ Configuration
|
|
298
|
+
|
|
299
|
+
All settings support **environment variables** with the `ARAXYS_` prefix:
|
|
300
|
+
|
|
301
|
+
```bash
|
|
302
|
+
export ARAXYS_SECRET_KEY="your-production-secret-key-here!"
|
|
303
|
+
export ARAXYS_REDIS_URL="redis://localhost:6379"
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
Or configure programmatically:
|
|
307
|
+
|
|
308
|
+
```python
|
|
309
|
+
from araxys import AraxysConfig, RateLimitConfig, HoneypotConfig
|
|
310
|
+
|
|
311
|
+
config = AraxysConfig(
|
|
312
|
+
secret_key="...",
|
|
313
|
+
rate_limit=RateLimitConfig(
|
|
314
|
+
max_requests=200,
|
|
315
|
+
window_seconds=120,
|
|
316
|
+
ban_threshold=10,
|
|
317
|
+
ban_duration_seconds=600,
|
|
318
|
+
escalation_multiplier=3.0,
|
|
319
|
+
),
|
|
320
|
+
honeypot=HoneypotConfig(
|
|
321
|
+
paths=["/admin", "/wp-login.php", "/.git/config"],
|
|
322
|
+
ban_duration_seconds=7200,
|
|
323
|
+
),
|
|
324
|
+
jwt={"access_token_ttl_minutes": 15, "refresh_token_ttl_days": 30},
|
|
325
|
+
audit={"encrypt": True, "log_file": "/var/log/araxys/audit.log"},
|
|
326
|
+
)
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
## 🧪 Testing
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
# Run all tests
|
|
335
|
+
uv run pytest tests/ -v
|
|
336
|
+
|
|
337
|
+
# With coverage
|
|
338
|
+
uv run pytest tests/ --cov=araxys --cov-report=term-missing
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**63 tests** covering all 7 modules — rate limiting, honeypots, API keys, JWT, headers, sanitization, and audit logging.
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## 🏭 Production Recommendations
|
|
346
|
+
|
|
347
|
+
| Aspect | Recommendation |
|
|
348
|
+
|--------|---------------|
|
|
349
|
+
| **Backends** | Use Redis (`araxys[redis]`) for multi-worker deployments |
|
|
350
|
+
| **Secret Key** | Generate with `openssl rand -hex 32` — never hardcode |
|
|
351
|
+
| **API Key Storage** | Implement `APIKeyStorage` protocol with your database |
|
|
352
|
+
| **Audit Logs** | Enable encryption + write to a dedicated log file |
|
|
353
|
+
| **Rate Limits** | Tune `max_requests` and `ban_threshold` per endpoint |
|
|
354
|
+
| **HTTPS** | Always deploy behind TLS — HSTS headers expect it |
|
|
355
|
+
|
|
356
|
+
---
|
|
357
|
+
|
|
358
|
+
## 📁 Tech Stack
|
|
359
|
+
|
|
360
|
+
<p align="center">
|
|
361
|
+
<img src="https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white" alt="Python">
|
|
362
|
+
<img src="https://img.shields.io/badge/FastAPI-009688?style=for-the-badge&logo=fastapi&logoColor=white" alt="FastAPI">
|
|
363
|
+
<img src="https://img.shields.io/badge/Pydantic-E92063?style=for-the-badge&logo=pydantic&logoColor=white" alt="Pydantic">
|
|
364
|
+
<img src="https://img.shields.io/badge/JWT-000000?style=for-the-badge&logo=jsonwebtokens&logoColor=white" alt="JWT">
|
|
365
|
+
<img src="https://img.shields.io/badge/Redis-DC382D?style=for-the-badge&logo=redis&logoColor=white" alt="Redis">
|
|
366
|
+
<img src="https://img.shields.io/badge/structlog-4B8BBE?style=for-the-badge&logo=python&logoColor=white" alt="structlog">
|
|
367
|
+
<img src="https://img.shields.io/badge/cryptography-AES256-DC143C?style=for-the-badge" alt="cryptography">
|
|
368
|
+
<img src="https://img.shields.io/badge/pytest-0A9EDC?style=for-the-badge&logo=pytest&logoColor=white" alt="pytest">
|
|
369
|
+
<img src="https://img.shields.io/badge/Ruff-D7FF64?style=for-the-badge&logo=ruff&logoColor=black" alt="Ruff">
|
|
370
|
+
<img src="https://img.shields.io/badge/mypy-strict-blue?style=for-the-badge" alt="mypy">
|
|
371
|
+
<img src="https://img.shields.io/badge/uv-DE5FE9?style=for-the-badge&logo=uv&logoColor=white" alt="uv">
|
|
372
|
+
</p>
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## 📄 License
|
|
377
|
+
|
|
378
|
+
MIT License — see [LICENSE](LICENSE) for details.
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
<p align="center">
|
|
383
|
+
<strong>Built with 🛡️ by <a href="https://github.com/andresramirez">Samuel Esteban Urrego Valencia</a></strong><br>
|
|
384
|
+
<em>"Security shouldn't be an afterthought — it should be a single import."</em>
|
|
385
|
+
</p>
|