This repository is a learning project for building a modern API-first system with:
- A FastAPI backend for auth, projects, secrets, and AI-assisted querying
- A Typer-based CLI (
envctl) that consumes the backend API - PostgreSQL + SQLAlchemy (async) for persistence
- Redis for OTP flows and transient auth state
- Gemini models for leak detection and semantic secret lookup
Note: The goal is educational to practice real architecture patterns and integrations in a small but complete system.
At a high level, this system lets users:
- Register and activate accounts using OTP emailed to them
- Log in with JWT access/refresh tokens
- Create and manage projects with role-based access control
- Store secrets per project (encrypted at rest)
- Query secrets using an AI-assisted "ask" flow (semantic search + tool calling)
- Access all of this from both REST endpoints and a local CLI
This codebase has two top-level apps:
fastapi-learning/
backend/ # FastAPI service
cli/ # Typer command line client (envctl)
app/api/v1: HTTP routers (auth,project,secret,ai)app/services: business logic (auth, project, secrets, embeddings, gemini, otp)app/dependencies: FastAPI dependency layers (auth guards, role checks)app/models: SQLAlchemy ORM modelsapp/schemas: request/response contracts (Pydantic)app/domain: service-level dataclasses (input/output shapes)app/core: config, security, exception system, app lifespanapp/db: async session, Redis pool, shared DB primitivesalembic/: migrations and migration environment
commands: user-facing commands (login,project,secret,audit)core/client.py: HTTP adapter to backend endpointscore/auth_store.py: local token persistence and JWT expiry checkscore/config_store.py: local app dirs, cache, and base URL configcore/ui.py: Rich-based terminal error panels
The backend follows a clean layered style:
- Router layer (
api/v1)
- Validates request payloads using schemas
- Wires dependencies and calls services
- Maps service output to response models
- Dependency layer (
dependencies)
- Resolves current user from bearer token
- Enforces project membership/role authorization
- Builds a typed
ProjectContextpassed into services
- Service layer (
services)
- Implements core use cases:
AuthService: register, activate, login, refreshProjectService: CRUD + membership managementSecretsService: encrypt/decrypt secret values + access checksEmbeddingService: generate/store embeddings + similarity rankingGeminiService: leak scan + streamed query with function callingAuditLogService: structured security/audit events
- Data layer (
models,db)
- SQLAlchemy async ORM entities with relationships
- Async session lifecycle and commit/rollback handling
- Redis connection pool for OTP and cooldown logic
- Cross-cutting (
core)
config.py: env-driven settingssecurity.py: JWT creation/verificationexceptions.py+err_handlers.py: normalized API errorslifespan.py: startup/shutdown hooks (Redis pool)
- Registration stores inactive user + sends OTP email
- OTP is stored in Redis as a SHA-256 hash with TTL
- Activation verifies OTP, activates the account, issues tokens
- Login returns access + refresh tokens
- Refresh endpoint rotates access token via refresh token validation
- Projects have members with roles:
admin,dev,readonly - Access checks happen via
require_project_role(...) - Routes receive a validated
ProjectContext(user + project + role)
- On project creation, a per-project DEK is generated and wrapped
- Secret values are encrypted with the project DEK before DB write
- Decryption happens only at read-time in service logic
- Some role/environment combinations are intentionally restricted
- Security-relevant actions (view/create/update/delete secret) are logged
- Logs include type, status, user, project, and details
- Optional pre-create leak scan using Gemini structured output
- Embedding-based key retrieval (
gemini-embedding-001) - SSE streaming endpoint for
askflow - Gemini tool-calling chooses either:
get_secretno_secret_match
The CLI is designed as a thin API client with local UX state.
Typerprovides command parsing and helpRichprovides tables/panels for friendly terminal outputhttpxhandles all HTTP requests- Tokens are stored in
~/.envctl/tokens.json(orENVCTL_HOME) - Access token expiry is checked locally by decoding JWT payload
- If access token is expired, CLI attempts refresh before request
- Project name to ID lookup is cached under
~/.envctl/cache/projects.json
Auth:
POST /api/v1/auth/registerPOST /api/v1/auth/activate-userPOST /api/v1/auth/loginPOST /api/v1/auth/resend-otpPOST /api/v1/auth/refresh-token
Projects:
GET /api/v1/projects/GET /api/v1/projects/{project_id}GET /api/v1/projects/name/?name=...POST /api/v1/projects/PUT /api/v1/projects/{project_id}DELETE /api/v1/projects/{project_id}GET /api/v1/projects/{project_id}/usersPOST /api/v1/projects/{project_id}/usersDELETE /api/v1/projects/{project_id}/users
Secrets:
GET /api/v1/projects/{project_id}/secrets/GET /api/v1/projects/{project_id}/secrets/{secret_id}GET /api/v1/projects/{project_id}/secrets/by-name/{secret_name}POST /api/v1/projects/{project_id}/secrets/PUT /api/v1/projects/{project_id}/secrets/{secret_id}DELETE /api/v1/projects/{project_id}/secrets/{secret_id}
AI:
POST /api/v1/projects/{project_id}/secrets/query(SSE stream)
This project demonstrates practical usage of:
- FastAPI router composition and dependency injection
- Pydantic request/response contracts
- Async SQLAlchemy ORM patterns (
select,update, async sessions) - PostgreSQL schema evolution with Alembic migrations
- Redis-backed OTP, cooldown, and attempt tracking
- JWT auth (access + refresh token strategy)
- Role-based authorization at dependency level
- Service-oriented layering and domain DTOs
- Field-level encryption with Fernet
- Structured exception modeling and centralized error translation
- Server-Sent Events (SSE) streaming responses
- LLM function-calling and embedding similarity retrieval
- CLI UX patterns with Typer + Rich
- Local credential/cache persistence with atomic file writes
- Multi-stage Docker builds for lean runtime images
- Python 3.11+
- PostgreSQL
- Redis
uvpackage manager- (Optional but needed for AI features) Google API key
cd backend
uv sync
uv run alembic upgrade head
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --reloadcd cli
uv sync
uv run envctl --helpRun the command from the cli directory. If you are in the repository root, use:
uv run --project cli envctl --helpIf you still see Failed to spawn: envctl, reinstall the local CLI package into the uv environment:
cd cli
uv sync --reinstall-package cli
uv run envctl --helpenvctl register
envctl user-activate
envctl login
envctl project create
envctl secret create my-project API_KEY "xxx" --env dev
envctl secret ask "what is the API key for staging?" --project my-projectBackend uses:
rufffor lintingmypyfor static type checking
Run checks:
cd backend
uv run ruff check .
uv run mypyAuto-fix where possible:
uv run ruff check . --fix- The CLI
auditcommand is currently a placeholder. - Some production hardening is still pending (secrets handling UX, broader tests, stricter env separation).
- This is intentionally a learning-oriented codebase, not a finished production platform.