-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapp.py
More file actions
110 lines (81 loc) · 3.92 KB
/
Copy pathapp.py
File metadata and controls
110 lines (81 loc) · 3.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
"""
Secure Password Manager API - FastAPI Application
Zero-knowledge password storage with client-side encryption
"""
import os
from datetime import datetime, timezone
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.exceptions import HTTPException
from dotenv import load_dotenv
from slowapi import _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded
from src.logger import logger
from src.routes.auth import router as auth_router
from src.routes.entries import router as entries_router
from src.middleware.rate_limit import limiter
load_dotenv()
description = """
**Created by Syed Basit Sherazi**
A secure, zero-knowledge password manager API with client-side AES-256-GCM encryption.
## Purpose
This project demonstrates secure API design principles including JWT authentication with token rotation,
zero-knowledge architecture, AES-256-GCM encryption, audit logging, and rate limiting —
built as a portfolio project to showcase backend security engineering skills.
---
## How to Authenticate
1. **Register** — `POST /auth/register` with a `username` and `password` (min 20 chars, must include uppercase, lowercase, digit, and special character)
2. **Login** — `POST /auth/login` — returns an `accessToken`, `refreshToken`, and `encryption_salt`
3. **Authorize** — click the **Authorize** button at the top of this page, enter `Bearer <your accessToken>`
4. All `/entries` endpoints are now unlocked
Access tokens expire in 35 minutes. Use `POST /auth/token` with your `refreshToken` to get a new one.
---
## How Zero-Knowledge Encryption Works
Your passwords are **never stored in plaintext** — not even on the server.
1. When you log in, the server returns an `encryption_salt` unique to your account
2. Your client combines your master password + salt to derive a 256-bit AES key (this never leaves your device)
3. Each password entry is encrypted with AES-256-GCM **before** being sent to the API
4. The server stores only the encrypted `ciphertext`, `iv`, and `tag` — it cannot decrypt them
5. Decryption happens on your device using the same derived key
This means even if the database is compromised, your passwords are safe.
"""
app = FastAPI(
title="Secure Password Manager API",
version="1.0.0",
description=description,
contact={"name": "Syed Basit Sherazi"},
)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
return JSONResponse(status_code=exc.status_code, content={"error": exc.detail})
@app.middleware("http")
async def security_headers(request: Request, call_next):
response = await call_next(request)
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
response.headers["X-XSS-Protection"] = "1; mode=block"
response.headers["Strict-Transport-Security"] = (
"max-age=31536000; includeSubDomains"
)
response.headers["Content-Security-Policy"] = (
"default-src 'self'; "
"script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; "
"style-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; "
"img-src 'self' data: https:"
)
return response
app.include_router(auth_router)
app.include_router(entries_router)
@app.get("/health")
def health():
return {"status": "ok", "timestamp": datetime.now(timezone.utc).isoformat()}
if __name__ == "__main__":
import uvicorn
PORT = int(os.getenv("PORT", 4000))
# Binding to all interfaces is required so the service is reachable from
# outside its container (Docker/Railway). Override with HOST if needed.
HOST = os.getenv("HOST", "0.0.0.0") # nosec B104 - intentional container bind
logger.info(f"Starting Secure Password Manager API on port {PORT}")
uvicorn.run("app:app", host=HOST, port=PORT, reload=False)