fastapi-payloadshield 1.0.0__tar.gz → 1.2.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.
- fastapi_payloadshield-1.2.0/PKG-INFO +257 -0
- fastapi_payloadshield-1.2.0/README.md +225 -0
- {fastapi_payloadshield-1.0.0 → fastapi_payloadshield-1.2.0}/document/DEVELOPMENT.md +35 -87
- fastapi_payloadshield-1.2.0/document/PROJECT_JOURNEY.md +105 -0
- {fastapi_payloadshield-1.0.0 → fastapi_payloadshield-1.2.0}/document/PUBLISHING_GUIDE.md +12 -11
- {fastapi_payloadshield-1.0.0 → fastapi_payloadshield-1.2.0}/examples/__init__.py +1 -1
- fastapi_payloadshield-1.2.0/examples/example_app.py +295 -0
- fastapi_payloadshield-1.2.0/examples/test_all_crypts.py +204 -0
- fastapi_payloadshield-1.2.0/fastapi_payloadshield/AESGCM256EncryptionHandler.py +65 -0
- fastapi_payloadshield-1.2.0/fastapi_payloadshield/Base64EncryptionHandler.py +33 -0
- fastapi_payloadshield-1.2.0/fastapi_payloadshield/EncryptionHandler.py +47 -0
- fastapi_payloadshield-1.2.0/fastapi_payloadshield/FernetEncryptionHandler.py +38 -0
- fastapi_payloadshield-1.2.0/fastapi_payloadshield/HybridRSAEncryptionHandler.py +93 -0
- fastapi_payloadshield-1.2.0/fastapi_payloadshield/__init__.py +42 -0
- fastapi_payloadshield-1.2.0/fastapi_payloadshield/config.py +53 -0
- fastapi_payloadshield-1.2.0/fastapi_payloadshield/crypto.py +55 -0
- fastapi_payloadshield-1.2.0/fastapi_payloadshield/decorators.py +174 -0
- fastapi_payloadshield-1.2.0/fastapi_payloadshield.egg-info/PKG-INFO +257 -0
- fastapi_payloadshield-1.2.0/fastapi_payloadshield.egg-info/SOURCES.txt +30 -0
- {fastapi_payloadshield-1.0.0 → fastapi_payloadshield-1.2.0}/fastapi_payloadshield.egg-info/requires.txt +1 -0
- {fastapi_payloadshield-1.0.0 → fastapi_payloadshield-1.2.0}/pyproject.toml +4 -4
- fastapi_payloadshield-1.2.0/setup.py +10 -0
- fastapi_payloadshield-1.2.0/tests/test_all_crypts.py +147 -0
- fastapi_payloadshield-1.2.0/tests/test_config.py +65 -0
- fastapi_payloadshield-1.2.0/tests/test_decorators.py +68 -0
- fastapi_payloadshield-1.2.0/tests/test_handlers.py +77 -0
- fastapi_payloadshield-1.0.0/PKG-INFO +0 -364
- fastapi_payloadshield-1.0.0/README.md +0 -327
- fastapi_payloadshield-1.0.0/document/COMPLETION_SUMMARY.md +0 -426
- fastapi_payloadshield-1.0.0/document/CUSTOM_HANDLERS.md +0 -391
- fastapi_payloadshield-1.0.0/document/DELIVERY_CHECKLIST.md +0 -386
- fastapi_payloadshield-1.0.0/document/FINAL_SUMMARY.md +0 -369
- fastapi_payloadshield-1.0.0/document/PACKAGE_SUMMARY.md +0 -361
- fastapi_payloadshield-1.0.0/document/PUBLICATION_READY.md +0 -337
- fastapi_payloadshield-1.0.0/document/QUICKSTART.md +0 -212
- fastapi_payloadshield-1.0.0/document/QUICK_REFERENCE.md +0 -367
- fastapi_payloadshield-1.0.0/document/REFACTORING_SUMMARY.md +0 -306
- fastapi_payloadshield-1.0.0/examples/example_app.py +0 -233
- fastapi_payloadshield-1.0.0/fastapi_payloadshield/__init__.py +0 -43
- fastapi_payloadshield-1.0.0/fastapi_payloadshield/crypto.py +0 -206
- fastapi_payloadshield-1.0.0/fastapi_payloadshield/decorators.py +0 -229
- fastapi_payloadshield-1.0.0/fastapi_payloadshield.egg-info/PKG-INFO +0 -364
- fastapi_payloadshield-1.0.0/fastapi_payloadshield.egg-info/SOURCES.txt +0 -27
- fastapi_payloadshield-1.0.0/setup.py +0 -46
- {fastapi_payloadshield-1.0.0 → fastapi_payloadshield-1.2.0}/LICENSE +0 -0
- {fastapi_payloadshield-1.0.0 → fastapi_payloadshield-1.2.0}/MANIFEST.in +0 -0
- {fastapi_payloadshield-1.0.0 → fastapi_payloadshield-1.2.0}/examples/test_client.py +0 -0
- {fastapi_payloadshield-1.0.0 → fastapi_payloadshield-1.2.0}/fastapi_payloadshield.egg-info/dependency_links.txt +0 -0
- {fastapi_payloadshield-1.0.0 → fastapi_payloadshield-1.2.0}/fastapi_payloadshield.egg-info/top_level.txt +0 -0
- {fastapi_payloadshield-1.0.0 → fastapi_payloadshield-1.2.0}/setup.cfg +0 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fastapi_payloadshield
|
|
3
|
+
Version: 1.2.0
|
|
4
|
+
Summary: Pluggable FastAPI decorators for encrypting/decrypting request and response payloads
|
|
5
|
+
Author-email: Ganesh Kandu <kanduganesh@gmail.com>
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/PayloadShield/FastAPIPS
|
|
8
|
+
Project-URL: Repository, https://github.com/PayloadShield/FastAPIPS.git
|
|
9
|
+
Project-URL: Issues, https://github.com/PayloadShield/FastAPIPS/issues
|
|
10
|
+
Keywords: fastapi,base64,crypto,encryption,decorator
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Development Status :: 4 - Beta
|
|
19
|
+
Classifier: Intended Audience :: Developers
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Framework :: FastAPI
|
|
22
|
+
Requires-Python: >=3.7
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: fastapi>=0.68.0
|
|
26
|
+
Requires-Dist: starlette>=0.19.0
|
|
27
|
+
Requires-Dist: cryptography>=41.0.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=6.0; extra == "dev"
|
|
30
|
+
Requires-Dist: pytest-asyncio>=0.18.0; extra == "dev"
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
|
|
33
|
+
# FastAPI Payload Shield
|
|
34
|
+
|
|
35
|
+
Pluggable FastAPI decorators for encrypting and decrypting request and
|
|
36
|
+
response payloads. Configure your keys once, then annotate any route with
|
|
37
|
+
`@PayloadShield.encrypt`, `@PayloadShield.decrypt`, or `@PayloadShield.crypt`.
|
|
38
|
+
|
|
39
|
+
> **Breaking change (v3.0.0)**: the old `encrypt_response` /
|
|
40
|
+
> `decrypt_request` / `crypto_middleware` decorators and the old
|
|
41
|
+
> `PayloadShieldEnc("base64")` factory function have been removed. There is
|
|
42
|
+
> no backward-compatible alias — see [Migrating to v3](#migrating-to-v3)
|
|
43
|
+
> below.
|
|
44
|
+
|
|
45
|
+
## Key Features
|
|
46
|
+
|
|
47
|
+
- **Pluggable encryption**: base64, Fernet, AES-GCM-256, and Hybrid RSA+AES
|
|
48
|
+
ship out of the box; register your own with `register_handler(...)`.
|
|
49
|
+
- **One-time key configuration**: `PayloadShieldEnc.init({...})` sets keys
|
|
50
|
+
globally for all decorators.
|
|
51
|
+
- **Route-agnostic**: no changes needed to your route logic besides adding a
|
|
52
|
+
decorator.
|
|
53
|
+
- **Async-friendly**: works with FastAPI's async route handlers.
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install fastapi_payloadshield
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Quick Start
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
from fastapi import FastAPI
|
|
65
|
+
from fastapi_payloadshield import PayloadShield, PayloadShieldEnc
|
|
66
|
+
|
|
67
|
+
# Configure encryption keys once, at startup.
|
|
68
|
+
PayloadShieldEnc.init({
|
|
69
|
+
"Key": "my-symmetric-key",
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
app = FastAPI()
|
|
73
|
+
|
|
74
|
+
@app.get("/api/data")
|
|
75
|
+
@PayloadShield.encrypt("base64")
|
|
76
|
+
async def get_data():
|
|
77
|
+
return {"message": "hello", "data": "world"}
|
|
78
|
+
|
|
79
|
+
@app.post("/api/process")
|
|
80
|
+
@PayloadShield.decrypt("base64")
|
|
81
|
+
async def process_data(data: dict):
|
|
82
|
+
return {"received": data, "status": "success"}
|
|
83
|
+
|
|
84
|
+
@app.post("/api/secure")
|
|
85
|
+
@PayloadShield.crypt("base64")
|
|
86
|
+
async def secure_endpoint(data: dict):
|
|
87
|
+
return {"processed": data}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Initialization: `PayloadShieldEnc.init(...)`
|
|
91
|
+
|
|
92
|
+
Call once before serving requests. Every decorator reads this shared
|
|
93
|
+
configuration at call time.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
PayloadShieldEnc.init({
|
|
97
|
+
"Key": key, # symmetric key: fernet, aes-gcm-256
|
|
98
|
+
"PrivateKey": "string", # RSA/hybrid private key (file path or PEM content)
|
|
99
|
+
"PublicKey": "string", # RSA/hybrid public key (file path or PEM content)
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
| Field | Used by | Accepts |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| `Key` | `fernet`, `aes-gcm-256` | Raw key string. `aes-gcm-256` requires the key to resolve to exactly 32 bytes (UTF-8 or base64 encoded). |
|
|
106
|
+
| `PrivateKey` | `rsa-hybrid` (decrypt) | File path to a PEM file, or the raw PEM content. |
|
|
107
|
+
| `PublicKey` | `rsa-hybrid` (encrypt) | File path to a PEM file, or the raw PEM content. |
|
|
108
|
+
|
|
109
|
+
Only set the fields required by the encryption types you actually use.
|
|
110
|
+
|
|
111
|
+
## Decorators
|
|
112
|
+
|
|
113
|
+
All three live on the `PayloadShield` class and take an `encryption_type`
|
|
114
|
+
(default `"base64"`).
|
|
115
|
+
|
|
116
|
+
### `@PayloadShield.encrypt(encryption_type)`
|
|
117
|
+
|
|
118
|
+
Encrypts the response payload only.
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
@app.get("/api/users")
|
|
122
|
+
@PayloadShield.encrypt("base64")
|
|
123
|
+
async def get_users():
|
|
124
|
+
return [{"id": 1, "name": "Alice"}]
|
|
125
|
+
|
|
126
|
+
# Response: {"encrypted": "W3siaWQiOiAxLCAibmFtZSI6ICJBbGljZSJ9XQ=="}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### `@PayloadShield.decrypt(encryption_type)`
|
|
130
|
+
|
|
131
|
+
Decrypts the request payload only; the route receives the decrypted dict.
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
@app.post("/api/login")
|
|
135
|
+
@PayloadShield.decrypt("base64")
|
|
136
|
+
async def login(credentials: dict):
|
|
137
|
+
return {"status": "success"}
|
|
138
|
+
|
|
139
|
+
# Expects: {"encrypted": "base64_encoded_json"}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### `@PayloadShield.crypt(encryption_type)`
|
|
143
|
+
|
|
144
|
+
Decrypts the request and encrypts the response.
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
@app.post("/api/secure")
|
|
148
|
+
@PayloadShield.crypt("base64")
|
|
149
|
+
async def secure_endpoint(data: dict):
|
|
150
|
+
return {"processed": data}
|
|
151
|
+
|
|
152
|
+
# Expects: {"encrypted": "encrypted_data"}
|
|
153
|
+
# Returns: {"encrypted": "encrypted_data"}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Built-in Encryption Handlers
|
|
157
|
+
|
|
158
|
+
| Name | Algorithm | Keys required | Security |
|
|
159
|
+
|---|---|---|---|
|
|
160
|
+
| `base64` | Base64 encoding | none | None — obfuscation only |
|
|
161
|
+
| `fernet` | Fernet (AES-128-CBC + HMAC) | `Key` | Symmetric, authenticated |
|
|
162
|
+
| `aes-gcm-256` | AES-256-GCM | `Key` (32 bytes) | Symmetric, authenticated |
|
|
163
|
+
| `rsa-hybrid` | RSA-OAEP + AES-256-GCM | `PublicKey` (encrypt), `PrivateKey` (decrypt) | Asymmetric/hybrid |
|
|
164
|
+
|
|
165
|
+
## Custom Handlers
|
|
166
|
+
|
|
167
|
+
Implement `EncryptionHandler` and register it — every decorator can then
|
|
168
|
+
use it by name.
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
from typing import Any, Dict, Optional
|
|
172
|
+
from fastapi_payloadshield import EncryptionHandler, register_handler, PayloadShield
|
|
173
|
+
|
|
174
|
+
class MyHandler(EncryptionHandler):
|
|
175
|
+
def encode(self, data: Any, config: Optional[Dict[str, Any]] = None) -> str:
|
|
176
|
+
...
|
|
177
|
+
|
|
178
|
+
def decode(self, encoded_data: str, config: Optional[Dict[str, Any]] = None) -> Any:
|
|
179
|
+
...
|
|
180
|
+
|
|
181
|
+
register_handler("my-handler", MyHandler())
|
|
182
|
+
|
|
183
|
+
@app.post("/api/custom")
|
|
184
|
+
@PayloadShield.crypt("my-handler")
|
|
185
|
+
async def custom_endpoint(data: dict):
|
|
186
|
+
return data
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`config` is the dict returned by `PayloadShieldEnc.get_config()` — pull out
|
|
190
|
+
whatever keys your handler needs (`Key`, `PrivateKey`, `PublicKey`).
|
|
191
|
+
|
|
192
|
+
## How It Works
|
|
193
|
+
|
|
194
|
+
**Request decryption**: client sends `{"encrypted": "..."}` → decorator
|
|
195
|
+
decodes it with the configured handler → route receives the plain dict.
|
|
196
|
+
|
|
197
|
+
**Response encryption**: route returns a dict → decorator encodes it with
|
|
198
|
+
the configured handler → client receives `{"encrypted": "..."}`.
|
|
199
|
+
|
|
200
|
+
## Errors
|
|
201
|
+
|
|
202
|
+
| Situation | Behavior |
|
|
203
|
+
|---|---|
|
|
204
|
+
| Request decryption fails | `400` response: `{"error": "Failed to decrypt request: ..."}` |
|
|
205
|
+
| Unknown `encryption_type` | `ValueError` raised when the decorator is applied: `Encryption handler '<name>' not found. Available handlers: ...` |
|
|
206
|
+
| Missing required key (e.g. no `Key` set for `fernet`) | `ValueError` raised when encoding/decoding: `... requires 'Key' to be set via PayloadShieldEnc.init(...)` |
|
|
207
|
+
|
|
208
|
+
## Migrating to v3
|
|
209
|
+
|
|
210
|
+
| Removed (v2) | Replacement (v3) |
|
|
211
|
+
|---|---|
|
|
212
|
+
| `PayloadShieldEnc("base64")` (decorator factory) | `PayloadShield.encrypt("base64")` |
|
|
213
|
+
| `PayloadShieldDec("base64")` | `PayloadShield.decrypt("base64")` |
|
|
214
|
+
| `PayloadShield("base64")` (function call) | `PayloadShield.crypt("base64")` |
|
|
215
|
+
| `encrypt_response`, `decrypt_request`, `crypto_middleware` | Use the decorators above |
|
|
216
|
+
| No key configuration API | `PayloadShieldEnc.init({...})` |
|
|
217
|
+
|
|
218
|
+
There is no compatibility shim — update all call sites to the new API.
|
|
219
|
+
|
|
220
|
+
## Testing
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
# Run the example app
|
|
224
|
+
python examples/example_app.py
|
|
225
|
+
|
|
226
|
+
# Exercise it with the sample client
|
|
227
|
+
python examples/test_client.py
|
|
228
|
+
|
|
229
|
+
# Run the test suite
|
|
230
|
+
pytest
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Requirements
|
|
234
|
+
|
|
235
|
+
- Python 3.7+
|
|
236
|
+
- FastAPI 0.68+
|
|
237
|
+
- Starlette 0.19+
|
|
238
|
+
- cryptography 41+
|
|
239
|
+
|
|
240
|
+
## Project Layout
|
|
241
|
+
|
|
242
|
+
- `fastapi_payloadshield/` - Main package
|
|
243
|
+
- `__init__.py` - Public exports
|
|
244
|
+
- `config.py` - `PayloadShieldEnc` key configuration
|
|
245
|
+
- `decorators.py` - `PayloadShield` decorators
|
|
246
|
+
- `crypto.py` - Handler registry (`register_handler`, `get_handler`)
|
|
247
|
+
- `EncryptionHandler.py`, `Base64EncryptionHandler.py`,
|
|
248
|
+
`FernetEncryptionHandler.py`, `AESGCM256EncryptionHandler.py`,
|
|
249
|
+
`HybridRSAEncryptionHandler.py` - Built-in handlers
|
|
250
|
+
- `examples/` - `example_app.py` demo app and `test_client.py` sample client
|
|
251
|
+
- `tests/` - pytest suite for handlers, config, and decorators
|
|
252
|
+
- `document/` - Additional guides (see [document/](document/))
|
|
253
|
+
|
|
254
|
+
## License
|
|
255
|
+
|
|
256
|
+
Apache-2.0 - See [LICENSE](LICENSE) for details.
|
|
257
|
+
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# FastAPI Payload Shield
|
|
2
|
+
|
|
3
|
+
Pluggable FastAPI decorators for encrypting and decrypting request and
|
|
4
|
+
response payloads. Configure your keys once, then annotate any route with
|
|
5
|
+
`@PayloadShield.encrypt`, `@PayloadShield.decrypt`, or `@PayloadShield.crypt`.
|
|
6
|
+
|
|
7
|
+
> **Breaking change (v3.0.0)**: the old `encrypt_response` /
|
|
8
|
+
> `decrypt_request` / `crypto_middleware` decorators and the old
|
|
9
|
+
> `PayloadShieldEnc("base64")` factory function have been removed. There is
|
|
10
|
+
> no backward-compatible alias — see [Migrating to v3](#migrating-to-v3)
|
|
11
|
+
> below.
|
|
12
|
+
|
|
13
|
+
## Key Features
|
|
14
|
+
|
|
15
|
+
- **Pluggable encryption**: base64, Fernet, AES-GCM-256, and Hybrid RSA+AES
|
|
16
|
+
ship out of the box; register your own with `register_handler(...)`.
|
|
17
|
+
- **One-time key configuration**: `PayloadShieldEnc.init({...})` sets keys
|
|
18
|
+
globally for all decorators.
|
|
19
|
+
- **Route-agnostic**: no changes needed to your route logic besides adding a
|
|
20
|
+
decorator.
|
|
21
|
+
- **Async-friendly**: works with FastAPI's async route handlers.
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install fastapi_payloadshield
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from fastapi import FastAPI
|
|
33
|
+
from fastapi_payloadshield import PayloadShield, PayloadShieldEnc
|
|
34
|
+
|
|
35
|
+
# Configure encryption keys once, at startup.
|
|
36
|
+
PayloadShieldEnc.init({
|
|
37
|
+
"Key": "my-symmetric-key",
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
app = FastAPI()
|
|
41
|
+
|
|
42
|
+
@app.get("/api/data")
|
|
43
|
+
@PayloadShield.encrypt("base64")
|
|
44
|
+
async def get_data():
|
|
45
|
+
return {"message": "hello", "data": "world"}
|
|
46
|
+
|
|
47
|
+
@app.post("/api/process")
|
|
48
|
+
@PayloadShield.decrypt("base64")
|
|
49
|
+
async def process_data(data: dict):
|
|
50
|
+
return {"received": data, "status": "success"}
|
|
51
|
+
|
|
52
|
+
@app.post("/api/secure")
|
|
53
|
+
@PayloadShield.crypt("base64")
|
|
54
|
+
async def secure_endpoint(data: dict):
|
|
55
|
+
return {"processed": data}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Initialization: `PayloadShieldEnc.init(...)`
|
|
59
|
+
|
|
60
|
+
Call once before serving requests. Every decorator reads this shared
|
|
61
|
+
configuration at call time.
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
PayloadShieldEnc.init({
|
|
65
|
+
"Key": key, # symmetric key: fernet, aes-gcm-256
|
|
66
|
+
"PrivateKey": "string", # RSA/hybrid private key (file path or PEM content)
|
|
67
|
+
"PublicKey": "string", # RSA/hybrid public key (file path or PEM content)
|
|
68
|
+
})
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
| Field | Used by | Accepts |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| `Key` | `fernet`, `aes-gcm-256` | Raw key string. `aes-gcm-256` requires the key to resolve to exactly 32 bytes (UTF-8 or base64 encoded). |
|
|
74
|
+
| `PrivateKey` | `rsa-hybrid` (decrypt) | File path to a PEM file, or the raw PEM content. |
|
|
75
|
+
| `PublicKey` | `rsa-hybrid` (encrypt) | File path to a PEM file, or the raw PEM content. |
|
|
76
|
+
|
|
77
|
+
Only set the fields required by the encryption types you actually use.
|
|
78
|
+
|
|
79
|
+
## Decorators
|
|
80
|
+
|
|
81
|
+
All three live on the `PayloadShield` class and take an `encryption_type`
|
|
82
|
+
(default `"base64"`).
|
|
83
|
+
|
|
84
|
+
### `@PayloadShield.encrypt(encryption_type)`
|
|
85
|
+
|
|
86
|
+
Encrypts the response payload only.
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
@app.get("/api/users")
|
|
90
|
+
@PayloadShield.encrypt("base64")
|
|
91
|
+
async def get_users():
|
|
92
|
+
return [{"id": 1, "name": "Alice"}]
|
|
93
|
+
|
|
94
|
+
# Response: {"encrypted": "W3siaWQiOiAxLCAibmFtZSI6ICJBbGljZSJ9XQ=="}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### `@PayloadShield.decrypt(encryption_type)`
|
|
98
|
+
|
|
99
|
+
Decrypts the request payload only; the route receives the decrypted dict.
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
@app.post("/api/login")
|
|
103
|
+
@PayloadShield.decrypt("base64")
|
|
104
|
+
async def login(credentials: dict):
|
|
105
|
+
return {"status": "success"}
|
|
106
|
+
|
|
107
|
+
# Expects: {"encrypted": "base64_encoded_json"}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### `@PayloadShield.crypt(encryption_type)`
|
|
111
|
+
|
|
112
|
+
Decrypts the request and encrypts the response.
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
@app.post("/api/secure")
|
|
116
|
+
@PayloadShield.crypt("base64")
|
|
117
|
+
async def secure_endpoint(data: dict):
|
|
118
|
+
return {"processed": data}
|
|
119
|
+
|
|
120
|
+
# Expects: {"encrypted": "encrypted_data"}
|
|
121
|
+
# Returns: {"encrypted": "encrypted_data"}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Built-in Encryption Handlers
|
|
125
|
+
|
|
126
|
+
| Name | Algorithm | Keys required | Security |
|
|
127
|
+
|---|---|---|---|
|
|
128
|
+
| `base64` | Base64 encoding | none | None — obfuscation only |
|
|
129
|
+
| `fernet` | Fernet (AES-128-CBC + HMAC) | `Key` | Symmetric, authenticated |
|
|
130
|
+
| `aes-gcm-256` | AES-256-GCM | `Key` (32 bytes) | Symmetric, authenticated |
|
|
131
|
+
| `rsa-hybrid` | RSA-OAEP + AES-256-GCM | `PublicKey` (encrypt), `PrivateKey` (decrypt) | Asymmetric/hybrid |
|
|
132
|
+
|
|
133
|
+
## Custom Handlers
|
|
134
|
+
|
|
135
|
+
Implement `EncryptionHandler` and register it — every decorator can then
|
|
136
|
+
use it by name.
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from typing import Any, Dict, Optional
|
|
140
|
+
from fastapi_payloadshield import EncryptionHandler, register_handler, PayloadShield
|
|
141
|
+
|
|
142
|
+
class MyHandler(EncryptionHandler):
|
|
143
|
+
def encode(self, data: Any, config: Optional[Dict[str, Any]] = None) -> str:
|
|
144
|
+
...
|
|
145
|
+
|
|
146
|
+
def decode(self, encoded_data: str, config: Optional[Dict[str, Any]] = None) -> Any:
|
|
147
|
+
...
|
|
148
|
+
|
|
149
|
+
register_handler("my-handler", MyHandler())
|
|
150
|
+
|
|
151
|
+
@app.post("/api/custom")
|
|
152
|
+
@PayloadShield.crypt("my-handler")
|
|
153
|
+
async def custom_endpoint(data: dict):
|
|
154
|
+
return data
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`config` is the dict returned by `PayloadShieldEnc.get_config()` — pull out
|
|
158
|
+
whatever keys your handler needs (`Key`, `PrivateKey`, `PublicKey`).
|
|
159
|
+
|
|
160
|
+
## How It Works
|
|
161
|
+
|
|
162
|
+
**Request decryption**: client sends `{"encrypted": "..."}` → decorator
|
|
163
|
+
decodes it with the configured handler → route receives the plain dict.
|
|
164
|
+
|
|
165
|
+
**Response encryption**: route returns a dict → decorator encodes it with
|
|
166
|
+
the configured handler → client receives `{"encrypted": "..."}`.
|
|
167
|
+
|
|
168
|
+
## Errors
|
|
169
|
+
|
|
170
|
+
| Situation | Behavior |
|
|
171
|
+
|---|---|
|
|
172
|
+
| Request decryption fails | `400` response: `{"error": "Failed to decrypt request: ..."}` |
|
|
173
|
+
| Unknown `encryption_type` | `ValueError` raised when the decorator is applied: `Encryption handler '<name>' not found. Available handlers: ...` |
|
|
174
|
+
| Missing required key (e.g. no `Key` set for `fernet`) | `ValueError` raised when encoding/decoding: `... requires 'Key' to be set via PayloadShieldEnc.init(...)` |
|
|
175
|
+
|
|
176
|
+
## Migrating to v3
|
|
177
|
+
|
|
178
|
+
| Removed (v2) | Replacement (v3) |
|
|
179
|
+
|---|---|
|
|
180
|
+
| `PayloadShieldEnc("base64")` (decorator factory) | `PayloadShield.encrypt("base64")` |
|
|
181
|
+
| `PayloadShieldDec("base64")` | `PayloadShield.decrypt("base64")` |
|
|
182
|
+
| `PayloadShield("base64")` (function call) | `PayloadShield.crypt("base64")` |
|
|
183
|
+
| `encrypt_response`, `decrypt_request`, `crypto_middleware` | Use the decorators above |
|
|
184
|
+
| No key configuration API | `PayloadShieldEnc.init({...})` |
|
|
185
|
+
|
|
186
|
+
There is no compatibility shim — update all call sites to the new API.
|
|
187
|
+
|
|
188
|
+
## Testing
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# Run the example app
|
|
192
|
+
python examples/example_app.py
|
|
193
|
+
|
|
194
|
+
# Exercise it with the sample client
|
|
195
|
+
python examples/test_client.py
|
|
196
|
+
|
|
197
|
+
# Run the test suite
|
|
198
|
+
pytest
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Requirements
|
|
202
|
+
|
|
203
|
+
- Python 3.7+
|
|
204
|
+
- FastAPI 0.68+
|
|
205
|
+
- Starlette 0.19+
|
|
206
|
+
- cryptography 41+
|
|
207
|
+
|
|
208
|
+
## Project Layout
|
|
209
|
+
|
|
210
|
+
- `fastapi_payloadshield/` - Main package
|
|
211
|
+
- `__init__.py` - Public exports
|
|
212
|
+
- `config.py` - `PayloadShieldEnc` key configuration
|
|
213
|
+
- `decorators.py` - `PayloadShield` decorators
|
|
214
|
+
- `crypto.py` - Handler registry (`register_handler`, `get_handler`)
|
|
215
|
+
- `EncryptionHandler.py`, `Base64EncryptionHandler.py`,
|
|
216
|
+
`FernetEncryptionHandler.py`, `AESGCM256EncryptionHandler.py`,
|
|
217
|
+
`HybridRSAEncryptionHandler.py` - Built-in handlers
|
|
218
|
+
- `examples/` - `example_app.py` demo app and `test_client.py` sample client
|
|
219
|
+
- `tests/` - pytest suite for handlers, config, and decorators
|
|
220
|
+
- `document/` - Additional guides (see [document/](document/))
|
|
221
|
+
|
|
222
|
+
## License
|
|
223
|
+
|
|
224
|
+
Apache-2.0 - See [LICENSE](LICENSE) for details.
|
|
225
|
+
|
|
@@ -6,21 +6,31 @@
|
|
|
6
6
|
FastAPIPS/
|
|
7
7
|
├── fastapi_payloadshield/ # Main package directory
|
|
8
8
|
│ ├── __init__.py # Package initialization & exports
|
|
9
|
-
│ ├──
|
|
10
|
-
│
|
|
9
|
+
│ ├── config.py # PayloadShieldEnc key configuration
|
|
10
|
+
│ ├── decorators.py # PayloadShield.encrypt/decrypt/crypt decorators
|
|
11
|
+
│ ├── crypto.py # Handler registry (register_handler/get_handler)
|
|
12
|
+
│ ├── EncryptionHandler.py # Abstract handler interface
|
|
13
|
+
│ ├── Base64EncryptionHandler.py # base64 handler
|
|
14
|
+
│ ├── FernetEncryptionHandler.py # fernet handler
|
|
15
|
+
│ ├── AESGCM256EncryptionHandler.py# aes-gcm-256 handler
|
|
16
|
+
│ └── HybridRSAEncryptionHandler.py# rsa-hybrid handler
|
|
11
17
|
│
|
|
12
18
|
├── examples/ # Example implementations
|
|
13
19
|
│ ├── example_app.py # Full-featured example FastAPI app
|
|
14
20
|
│ └── test_client.py # Client script to test the API
|
|
15
21
|
│
|
|
22
|
+
├── tests/ # pytest suite
|
|
23
|
+
│ ├── test_handlers.py # Handler encode/decode roundtrip tests
|
|
24
|
+
│ ├── test_config.py # PayloadShieldEnc.init() tests
|
|
25
|
+
│ └── test_decorators.py # Decorator + FastAPI TestClient tests
|
|
26
|
+
│
|
|
16
27
|
├── setup.py # Classic setup configuration (pip installable)
|
|
17
28
|
├── pyproject.toml # Modern Python project configuration
|
|
18
29
|
├── requirements.txt # Development & runtime dependencies
|
|
19
|
-
├── README.md #
|
|
20
|
-
├── QUICKSTART.md # Quick start guide
|
|
30
|
+
├── README.md # Full API documentation
|
|
21
31
|
├── MANIFEST.in # Files to include in distribution
|
|
22
32
|
├── .gitignore # Git ignore patterns
|
|
23
|
-
└── LICENSE #
|
|
33
|
+
└── LICENSE # Apache-2.0 License
|
|
24
34
|
```
|
|
25
35
|
|
|
26
36
|
## Installation for Development
|
|
@@ -78,72 +88,29 @@ curl -X POST http://localhost:8000/api/login \
|
|
|
78
88
|
-d '{"encrypted":"eyJ1c2VybmFtZSI6ICJhZG1pbiIsICJwYXNzd29yZCI6ICJwYXNzd29yZDEyMyJ9"}'
|
|
79
89
|
```
|
|
80
90
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
- `encode_base64(data)` - Encode data to base64
|
|
85
|
-
- `decode_base64(encoded_data)` - Decode base64 to data
|
|
86
|
-
- `encode_response(data)` - Wrap response in encryption format
|
|
87
|
-
- `decode_request(encoded_data)` - Decode request format
|
|
88
|
-
|
|
89
|
-
### 2. `decorators.py` - Decorators
|
|
90
|
-
- `@encrypt_response` - Encrypts response payload
|
|
91
|
-
- `@decrypt_request` - Decrypts request payload
|
|
92
|
-
- `@crypto_middleware` - Combined encryption/decryption
|
|
93
|
-
|
|
94
|
-
## Adding New Features
|
|
95
|
-
|
|
96
|
-
### Add a New Decorator
|
|
97
|
-
1. Edit `fastapi_payloadshield/decorators.py`
|
|
98
|
-
2. Add your decorator function
|
|
99
|
-
3. Export it in `fastapi_payloadshield/__init__.py`
|
|
100
|
-
|
|
101
|
-
Example:
|
|
102
|
-
```python
|
|
103
|
-
# In decorators.py
|
|
104
|
-
def my_new_decorator(func):
|
|
105
|
-
@wraps(func)
|
|
106
|
-
async def wrapper(*args, **kwargs):
|
|
107
|
-
# Your logic here
|
|
108
|
-
return await func(*args, **kwargs)
|
|
109
|
-
return wrapper
|
|
110
|
-
|
|
111
|
-
# In __init__.py
|
|
112
|
-
from .decorators import my_new_decorator
|
|
113
|
-
__all__ = [..., "my_new_decorator"]
|
|
91
|
+
### Option 3: Run the pytest Suite
|
|
92
|
+
```bash
|
|
93
|
+
pytest
|
|
114
94
|
```
|
|
115
95
|
|
|
116
|
-
|
|
117
|
-
1. Edit `fastapi_payloadshield/crypto.py`
|
|
118
|
-
2. Add your function
|
|
119
|
-
3. Export if needed in `__init__.py`
|
|
96
|
+
## Package Components
|
|
120
97
|
|
|
121
|
-
|
|
98
|
+
See [../README.md](../README.md) for the full API reference
|
|
99
|
+
(`PayloadShieldEnc.init`, `PayloadShield.encrypt/decrypt/crypt`, built-in
|
|
100
|
+
handlers, and custom handler registration). This guide only covers
|
|
101
|
+
day-to-day development workflows.
|
|
122
102
|
|
|
123
|
-
|
|
124
|
-
Edit version in:
|
|
125
|
-
- `setup.py`
|
|
126
|
-
- `pyproject.toml`
|
|
127
|
-
- `fastapi_payloadshield/__init__.py`
|
|
103
|
+
## Adding a New Built-in Handler
|
|
128
104
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
105
|
+
1. Create `fastapi_payloadshield/MyHandler.py` implementing
|
|
106
|
+
`EncryptionHandler.encode(data, config)` / `.decode(encoded_data, config)`.
|
|
107
|
+
2. Register it in `fastapi_payloadshield/crypto.py`'s `_HANDLERS` dict.
|
|
108
|
+
3. Export the class from `fastapi_payloadshield/__init__.py`.
|
|
109
|
+
4. Add a roundtrip test in `tests/test_handlers.py`.
|
|
133
110
|
|
|
134
|
-
|
|
135
|
-
```bash
|
|
136
|
-
python -m build
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
### 4. Upload to PyPI
|
|
140
|
-
```bash
|
|
141
|
-
# Test PyPI first (optional)
|
|
142
|
-
twine upload --repository testpypi dist/*
|
|
111
|
+
## Publishing to PyPI
|
|
143
112
|
|
|
144
|
-
|
|
145
|
-
twine upload dist/*
|
|
146
|
-
```
|
|
113
|
+
See [PUBLISHING_GUIDE.md](PUBLISHING_GUIDE.md) for the full release process.
|
|
147
114
|
|
|
148
115
|
## Code Style
|
|
149
116
|
|
|
@@ -162,27 +129,6 @@ flake8 fastapi_payloadshield examples
|
|
|
162
129
|
mypy fastapi_payloadshield
|
|
163
130
|
```
|
|
164
131
|
|
|
165
|
-
## Creating Tests
|
|
166
|
-
|
|
167
|
-
Create a `tests/` directory with pytest tests:
|
|
168
|
-
|
|
169
|
-
```python
|
|
170
|
-
# tests/test_crypto.py
|
|
171
|
-
import pytest
|
|
172
|
-
from fastapi_payloadshield.crypto import encode_base64, decode_base64
|
|
173
|
-
|
|
174
|
-
def test_encode_decode():
|
|
175
|
-
data = {"key": "value"}
|
|
176
|
-
encoded = encode_base64(data)
|
|
177
|
-
decoded = decode_base64(encoded)
|
|
178
|
-
assert decoded == data
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
Run tests:
|
|
182
|
-
```bash
|
|
183
|
-
pytest
|
|
184
|
-
```
|
|
185
|
-
|
|
186
132
|
## Contributing
|
|
187
133
|
|
|
188
134
|
1. Fork the repository
|
|
@@ -232,9 +178,11 @@ rm -rf build/ dist/ *.egg-info
|
|
|
232
178
|
|
|
233
179
|
- [FastAPI Documentation](https://fastapi.tiangolo.com/)
|
|
234
180
|
- [Python Packaging Guide](https://packaging.python.org/)
|
|
235
|
-
- [
|
|
181
|
+
- [cryptography library](https://cryptography.io/)
|
|
236
182
|
- [Setuptools Documentation](https://setuptools.readthedocs.io/)
|
|
237
183
|
|
|
238
184
|
## License
|
|
239
185
|
|
|
240
|
-
|
|
186
|
+
Apache-2.0 - See [../LICENSE](../LICENSE) for details.
|
|
187
|
+
|
|
188
|
+
|