Secure user management service built with FastAPI, deployed on Cloudflare Workers, with a dual-database Zero-Knowledge key architecture.
Agrinow follows a Zero-Knowledge design — your server never stores plain-text passwords or raw private keys.
User Password
│
▼
Argon2id KDF ──── (email as salt + server pepper + associated data)
│
▼
Master Key (32 bytes, never stored)
│
├──► Encrypt Ed25519 Private Key ──► AES-GCM ──► Supabase (DB2)
│
└──► Public Key ──────────────────────────────► Cloudflare D1 (DB1)
| What | Where | How |
|---|---|---|
| User profile + Public key | Cloudflare D1 (SQL) | SQLAlchemy |
| Encrypted private key + nonce | Supabase PostgreSQL | SQLAlchemy + psycopg2 |
| Secrets (pepper, JWT key) | Cloudflare Secrets Store | env.SECRET.get() async |
agrinow/
├── src/
│ ├── entry.py # FastAPI app entrypoint & Cloudflare Worker binding
│ ├── models.py # SQLAlchemy table definitions (Users)
│ ├── database/
│ │ ├── database.py # Cloudflare D1 engine + local SQLite fallback
│ │ └── database_space.py # Supabase PostgreSQL engine (UserKeys vault)
│ └── routers/
│ └── create_user.py # POST /create_user endpoint
├── wrangler.jsonc # Cloudflare Worker deployment config
├── pyproject.toml # Python dependencies
└── .env # Local development secrets (never commit!)
| Layer | Technology |
|---|---|
| API Framework | FastAPI |
| Deployment | Cloudflare Workers (Python) |
| Primary DB | Cloudflare D1 (SQLite-compatible SQL) |
| Key Vault DB | Supabase PostgreSQL |
| ORM | SQLAlchemy |
| KDF | Argon2id (via cryptography) |
| Asymmetric Crypto | Ed25519 |
| Symmetric Encryption | AES-GCM (256-bit) |
| Secrets Management | Cloudflare Secrets Store |
- Python 3.11+
- Install dependencies from
requirements.txt- This includes
psycopg2-binary,cryptography,PyJWT,fastapi,sqlalchemy,uvicorn, andgoogle-api-python-client(for Gmail API).
- This includes
git clone <your-repo-url>
cd agrinow
python -m venv env
env\Scripts\activate # Windows
source env/bin/activate # macOS/Linux
pip install -e .Ensure you have all required environment variables set securely. Never commit this file.
# Standard Auth Secrets
JWT_SECRET_KEY=<generate: python -c "import secrets; print(secrets.token_hex(32))">
ARGON2_SECRET_PEPPER=<generate same way>
ARGON2_ASSOCIATED_DATA=agrinow_auth_v1
SUPABASE_DATABASE_URL=postgresql://postgres:[PASSWORD]@db.[PROJECT].supabase.co:5432/postgres
# Google/Gmail API Integration Secrets (JSON Stringified)
GMAIL_TOKEN_FILE={"token": "..."}
GMAIL_CLIENT_SECRET_FILE={"web": {"client_id": "...", "client_secret": "..."}}cd src
uvicorn entry:app --reloadOpen http://127.0.0.1:8000/docs for the interactive Swagger UI.
# Create the store (one time only)
npx wrangler secrets-store store create agrinow-secrets --remote
# Get the Store ID
npx wrangler secrets-store store list --remote
# Add each secret (replace <STORE_ID> with real ID)
npx wrangler secrets-store secret create <STORE_ID> --name JWT_SECRET_KEY --scopes workers --remote
npx wrangler secrets-store secret create <STORE_ID> --name ARGON2_SECRET_PEPPER --scopes workers --remote
npx wrangler secrets-store secret create <STORE_ID> --name ARGON2_ASSOCIATED_DATA --scopes workers --remote
npx wrangler secrets-store secret create <STORE_ID> --name SUPABASE_DATABASE_URL --scopes workers --remoteReplace all store_id placeholder values with your actual Secrets Store ID.
npx wrangler deploynpx wrangler d1 execute agrinow --command "
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
email TEXT UNIQUE NOT NULL,
phone TEXT UNIQUE,
first_name TEXT, last_name TEXT,
public_key TEXT, language TEXT, role TEXT,
manager_id INTEGER, emp_type TEXT,
is_active BOOLEAN, hire_date DATE,
relive_date DATE, created_dt DATE
);"Run in your Supabase SQL Editor:
CREATE TABLE user_keys (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL UNIQUE,
encrypted_private_key TEXT NOT NULL,
nonce TEXT NOT NULL
);| Method | Path | Description |
|---|---|---|
GET |
/ |
Health check |
POST |
/create_user/ |
Register a new user |
{
"username": "john_doe",
"email": "john@example.com",
"phone": "+1234567890",
"first_name": "John",
"last_name": "Doe",
"password": "your_secure_password",
"language": "en",
"role": "farmer",
"manager_id": null,
"hire_date": "2025-01-15",
"relive_date": null,
"emp_type": "full_time"
}- Passwords never stored — used only to derive Master Key, then wiped from memory via
finallyblock. - Private keys encrypted — AES-GCM before storage; raw key wiped from memory immediately.
- Hard fail on missing secrets — missing pepper/JWT key returns
HTTP 500, never silently falls back to a weak default. - Duplicate check first — username/email is validated before any expensive crypto, preventing CPU exhaustion attacks.
- Atomic creation — if the Supabase key vault insert fails, the D1 user row is automatically rolled back. No orphaned records, ever.
MIT