Saltearse al contenido

Python REST API

Esta página aún no está disponible en tu idioma.

This example shows a complete Contextia setup for a Python REST API built with FastAPI. It covers authentication and authorization, demonstrating how specs, decisions, norms, and code annotations work together.

The project is an API backend for a task management application. It has user authentication (JWT-based), role-based authorization, and CRUD operations for tasks and projects.

taskflow-api/
├── .contextia/
│ ├── config.yaml
│ └── system/
│ ├── identity.md
│ ├── specs/
│ │ ├── SPEC-001.md # User authentication
│ │ ├── SPEC-002.md # Role-based authorization
│ │ └── SPEC-003.md # Task CRUD operations
│ ├── rationale/
│ │ ├── DEC-001.md # JWT over session cookies
│ │ └── DEC-002.md # RBAC over ABAC
│ └── norms/
│ ├── NORM-SEC-001.md # Password handling
│ └── NORM-API-001.md # Response format
├── src/
│ ├── auth/
│ │ ├── router.py
│ │ ├── service.py
│ │ ├── models.py
│ │ └── dependencies.py
│ ├── tasks/
│ │ ├── router.py
│ │ ├── service.py
│ │ └── models.py
│ └── core/
│ ├── config.py
│ ├── security.py
│ └── database.py
└── tests/
.contextia/config.yaml
project:
name: taskflow-api
languages:
- python
source_paths:
- src/
annotations:
prefix: "@"
comment_syntax:
python: "#"
check:
ignore_patterns:
- "tests/**"
- "alembic/**"
context:
max_tokens: 8000
.contextia/system/identity.md
---
project: taskflow-api
version: "1.0"
---
## What is this
TaskFlow API is a REST backend for a collaborative task management platform.
Built with FastAPI, SQLAlchemy, and PostgreSQL.
## Architecture
Single deployable service with domain modules: auth, tasks, projects.
Each module has a router (HTTP layer), service (business logic), and models (data).
Authentication uses JWT tokens. Authorization uses role-based access control.
## Conventions
- All endpoints return the standard envelope: { data, error, meta }.
- Service functions raise domain exceptions; routers convert to HTTP responses.
- Database access only through repository pattern in service layer.
- All passwords hashed with bcrypt. Never store or log plaintext passwords.
.contextia/system/specs/SPEC-001.md
---
id: SPEC-001
title: User authentication
description: JWT-based login, token refresh, and session validation.
status: approved
decisions:
- DEC-001
norms:
- NORM-SEC-001
paths:
- src/auth/router.py
- src/auth/service.py
- src/core/security.py
---
## Objective
Provide stateless user authentication using JWT tokens, supporting
login, token refresh, and logout.
## Behaviors
WHEN a user submits valid email and password to POST /auth/login
THEN the system returns an access token (15min expiry) and a refresh token (7day expiry).
WHEN a user submits invalid credentials to POST /auth/login
THEN the system returns HTTP 401 with error code "invalid_credentials".
WHEN a request includes a valid access token in the Authorization header
THEN the system extracts the user identity and proceeds with the request.
WHEN a request includes an expired access token
THEN the system returns HTTP 401 with error code "token_expired".
WHEN a user submits a valid refresh token to POST /auth/refresh
THEN the system returns a new access token and rotates the refresh token.
WHEN a user calls POST /auth/logout with a valid token
THEN the refresh token is invalidated and cannot be reused.
.contextia/system/rationale/DEC-001.md
---
id: DEC-001
title: JWT over session cookies
status: accepted
date: 2025-11-20
specs:
- SPEC-001
---
## Context
Need stateless authentication for horizontal scaling behind a load balancer.
Session store (Redis) adds operational complexity and a single point of failure.
## Decision
Use JWT (RS256) for access tokens. Short-lived (15 min) to limit exposure
from stolen tokens. Refresh tokens stored in database for revocation capability.
## Alternatives considered
- **Session cookies + Redis**: Simpler token management but adds infrastructure dependency.
- **Opaque tokens + database lookup**: Every request hits the database.
## Consequences
- No session store needed for access token validation.
- Refresh tokens require database storage (acceptable, infrequent operation).
- Token size is larger than session IDs (acceptable for API use).
.contextia/system/norms/NORM-SEC-001.md
---
id: NORM-SEC-001
title: Password handling
status: active
paths:
- src/auth/**
- src/core/security.py
---
## Rules
1. Passwords MUST be hashed with bcrypt (cost factor 12) before storage.
2. Plaintext passwords MUST NOT appear in logs, error messages, or API responses.
3. Password comparison MUST use constant-time comparison to prevent timing attacks.
4. Minimum password length is 8 characters. Enforce at the API validation layer.
5. Failed login attempts MUST be rate-limited to 5 per minute per IP address.
src/auth/router.py
from fastapi import APIRouter, Depends, HTTPException
from src.auth.service import AuthService
from src.auth.models import LoginRequest, TokenResponse
from src.core.security import get_current_user
router = APIRouter(prefix="/auth", tags=["auth"])
# @spec SPEC-001
# @decision DEC-001
@router.post("/login", response_model=TokenResponse)
async def login(request: LoginRequest, auth: AuthService = Depends()):
"""Authenticate user and return JWT token pair."""
result = auth.authenticate(request.email, request.password)
if result is None:
raise HTTPException(status_code=401, detail="invalid_credentials")
return result
# @spec SPEC-001
@router.post("/refresh", response_model=TokenResponse)
async def refresh_token(refresh_token: str, auth: AuthService = Depends()):
"""Exchange a valid refresh token for a new token pair."""
result = auth.refresh(refresh_token)
if result is None:
raise HTTPException(status_code=401, detail="invalid_refresh_token")
return result
# @spec SPEC-001
@router.post("/logout")
async def logout(user=Depends(get_current_user), auth: AuthService = Depends()):
"""Invalidate the user's refresh token."""
auth.revoke_refresh_token(user.id)
return {"data": None, "error": None, "meta": {}}
src/core/security.py
import bcrypt
from datetime import datetime, timedelta, timezone
from jose import jwt
from src.core.config import settings
# @spec SPEC-001
# @decision DEC-001
# @spec SPEC-002
def create_access_token(user_id: int, roles: list[str]) -> str:
"""Create a short-lived JWT access token with user roles."""
payload = {
"sub": str(user_id),
"roles": roles,
"exp": datetime.now(timezone.utc) + timedelta(minutes=15),
"type": "access",
}
return jwt.encode(payload, settings.jwt_private_key, algorithm="RS256")
# @norm NORM-SEC-001
def hash_password(password: str) -> str:
"""Hash password with bcrypt. Never store plaintext."""
return bcrypt.hashpw(
password.encode("utf-8"),
bcrypt.gensalt(rounds=12),
).decode("utf-8")
# @norm NORM-SEC-001
def verify_password(plain: str, hashed: str) -> bool:
"""Constant-time password comparison."""
return bcrypt.checkpw(plain.encode("utf-8"), hashed.encode("utf-8"))

When an agent works on a task related to authentication:

Terminal window
contextia context TASK-010

Output:

── Task: TASK-010 — Add password reset flow ──
specs: [SPEC-001]
── Spec: SPEC-001 — User authentication ──
[Full spec with all WHEN/THEN behaviors]
── Decision: DEC-001 — JWT over session cookies ──
[Decision with rationale and alternatives]
── Norm: NORM-SEC-001 — Password handling ──
[All 5 security rules]

The agent receives everything it needs to implement the password reset flow in a way that is consistent with existing patterns, respects security norms, and follows the JWT architecture.

Terminal window
contextia check
Checking .contextia/ integrity...
Specs: 3 found, 0 issues
Decisions: 2 found, 0 issues
Norms: 2 found, 0 issues
Annotations: 8 found, 0 orphans, 0 missing
All checks passed.