chore: initialize gondulf project structure
Set up Python project with uv environment management and FastAPI stack. Project structure: - src/gondulf/ - Main application package - tests/ - Test suite directory - pyproject.toml - Project configuration with dependencies - README.md - Project documentation - uv.lock - Dependency lock file Dependencies configured: - FastAPI + Uvicorn for web framework - SQLAlchemy for database ORM - pytest + coverage for testing - ruff, black, mypy, flake8 for code quality - Development environment using uv direct execution model All project standards reviewed and implemented per: - /docs/standards/coding.md - /docs/standards/testing.md - /docs/standards/git.md - /docs/standards/development-environment.md - /docs/standards/versioning.md
This commit is contained in:
61
docs/standards/README.md
Normal file
61
docs/standards/README.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# Project Standards
|
||||
|
||||
This directory contains all project-wide standards and conventions for the IndieAuth server implementation.
|
||||
|
||||
## Standard Documents
|
||||
|
||||
### Core Standards
|
||||
- **[versioning.md](versioning.md)** - Semantic versioning approach (v2.0.0)
|
||||
- **[git.md](git.md)** - Trunk-based development workflow
|
||||
- **[testing.md](testing.md)** - Testing strategy with 80% minimum coverage
|
||||
- **[coding.md](coding.md)** - Python coding standards and conventions
|
||||
- **[development-environment.md](development-environment.md)** - uv-based environment management and workflow
|
||||
|
||||
## Key Decisions
|
||||
|
||||
### Technology Stack (Approved)
|
||||
- **Language**: Python 3.10+
|
||||
- **Framework**: FastAPI
|
||||
- **Database**: SQLite
|
||||
- **Environment Management**: uv (with direct execution model)
|
||||
- **Testing**: pytest with 80% minimum coverage
|
||||
- **Code Quality**: Black, Ruff, mypy
|
||||
|
||||
### Development Workflow
|
||||
- **Git**: Trunk-based development on `main` branch
|
||||
- **Environments**: Direct execution via `uv run` (no activation)
|
||||
- **Testing**: TDD preferred, behavior-focused tests
|
||||
- **Documentation**: Comprehensive before implementation
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Daily Commands
|
||||
```bash
|
||||
# Run application
|
||||
uv run uvicorn src.main:app --reload
|
||||
|
||||
# Run tests
|
||||
uv run pytest
|
||||
|
||||
# Add dependency
|
||||
uv pip install package
|
||||
uv pip freeze > requirements.txt
|
||||
|
||||
# Code quality
|
||||
uv run ruff check .
|
||||
uv run mypy src
|
||||
```
|
||||
|
||||
### Standards Compliance
|
||||
All code must:
|
||||
- Pass linting (Ruff)
|
||||
- Pass type checking (mypy)
|
||||
- Have 80%+ test coverage (90% for new code)
|
||||
- Follow PEP 8 and project conventions
|
||||
- Be documented with clear docstrings
|
||||
|
||||
## Status
|
||||
|
||||
✅ **Standards Finalized**: 2025-11-20
|
||||
|
||||
All foundational standards have been defined and documented. These standards form the basis for all implementation work on the IndieAuth server project.
|
||||
377
docs/standards/coding.md
Normal file
377
docs/standards/coding.md
Normal file
@@ -0,0 +1,377 @@
|
||||
# Python Coding Standard
|
||||
|
||||
## Overview
|
||||
|
||||
This document defines coding standards for the IndieAuth server implementation in Python. The primary goal is maintainability and clarity over cleverness.
|
||||
|
||||
## Python Version
|
||||
|
||||
- **Target**: Python 3.10+ (for modern type hints and async support)
|
||||
- Use only stable language features
|
||||
- Avoid deprecated patterns
|
||||
|
||||
## Code Style
|
||||
|
||||
### Formatting
|
||||
- Use **Black** for automatic code formatting (line length: 88)
|
||||
- Use **isort** for import sorting
|
||||
- No manual formatting - let tools handle it
|
||||
|
||||
### Linting
|
||||
- Use **flake8** with the following configuration:
|
||||
```ini
|
||||
# .flake8
|
||||
[flake8]
|
||||
max-line-length = 88
|
||||
extend-ignore = E203, W503
|
||||
exclude = .git,__pycache__,docs,build,dist
|
||||
```
|
||||
|
||||
- Use **mypy** for static type checking:
|
||||
```ini
|
||||
# mypy.ini
|
||||
[mypy]
|
||||
python_version = 3.10
|
||||
warn_return_any = True
|
||||
warn_unused_configs = True
|
||||
disallow_untyped_defs = True
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
indieauth/
|
||||
├── __init__.py
|
||||
├── main.py # Application entry point
|
||||
├── config.py # Configuration management
|
||||
├── models/ # Data models
|
||||
│ ├── __init__.py
|
||||
│ ├── client.py
|
||||
│ ├── token.py
|
||||
│ └── user.py
|
||||
├── endpoints/ # HTTP endpoint handlers
|
||||
│ ├── __init__.py
|
||||
│ ├── authorization.py
|
||||
│ ├── token.py
|
||||
│ └── registration.py
|
||||
├── services/ # Business logic
|
||||
│ ├── __init__.py
|
||||
│ ├── auth_service.py
|
||||
│ ├── token_service.py
|
||||
│ └── client_service.py
|
||||
├── storage/ # Data persistence
|
||||
│ ├── __init__.py
|
||||
│ ├── base.py
|
||||
│ └── sqlite.py
|
||||
├── utils/ # Utility functions
|
||||
│ ├── __init__.py
|
||||
│ ├── crypto.py
|
||||
│ └── validation.py
|
||||
└── exceptions.py # Custom exceptions
|
||||
```
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
### General Rules
|
||||
- Use descriptive names - clarity over brevity
|
||||
- Avoid abbreviations except well-known ones (url, id, db)
|
||||
- Use American English spelling
|
||||
|
||||
### Specific Conventions
|
||||
- **Modules**: `lowercase_with_underscores.py`
|
||||
- **Classes**: `PascalCase`
|
||||
- **Functions/Methods**: `lowercase_with_underscores()`
|
||||
- **Constants**: `UPPERCASE_WITH_UNDERSCORES`
|
||||
- **Private**: Prefix with single underscore `_private_method()`
|
||||
- **Internal**: Prefix with double underscore `__internal_var`
|
||||
|
||||
### Examples
|
||||
```python
|
||||
# Good
|
||||
class ClientRegistration:
|
||||
MAX_REDIRECT_URIS = 10
|
||||
|
||||
def validate_redirect_uri(self, uri: str) -> bool:
|
||||
pass
|
||||
|
||||
# Bad
|
||||
class client_reg: # Wrong case
|
||||
maxURIs = 10 # Wrong case, abbreviation
|
||||
|
||||
def checkURI(self, u): # Unclear naming, missing types
|
||||
pass
|
||||
```
|
||||
|
||||
## Type Hints
|
||||
|
||||
Type hints are **mandatory** for all functions and methods:
|
||||
|
||||
```python
|
||||
from typing import Optional, List, Dict, Union
|
||||
from datetime import datetime
|
||||
|
||||
def generate_token(
|
||||
client_id: str,
|
||||
scope: Optional[str] = None,
|
||||
expires_in: int = 3600
|
||||
) -> Dict[str, Union[str, int, datetime]]:
|
||||
"""Generate an access token for the client."""
|
||||
pass
|
||||
```
|
||||
|
||||
## Docstrings
|
||||
|
||||
Use Google-style docstrings for all public modules, classes, and functions:
|
||||
|
||||
```python
|
||||
def exchange_code(
|
||||
code: str,
|
||||
client_id: str,
|
||||
code_verifier: Optional[str] = None
|
||||
) -> Token:
|
||||
"""
|
||||
Exchange authorization code for access token.
|
||||
|
||||
Args:
|
||||
code: The authorization code received from auth endpoint
|
||||
client_id: The client identifier
|
||||
code_verifier: PKCE code verifier if PKCE was used
|
||||
|
||||
Returns:
|
||||
Access token with associated metadata
|
||||
|
||||
Raises:
|
||||
InvalidCodeError: If code is invalid or expired
|
||||
InvalidClientError: If client_id doesn't match code
|
||||
PKCERequiredError: If PKCE is required but not provided
|
||||
"""
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Custom Exceptions
|
||||
Define specific exceptions in `exceptions.py`:
|
||||
|
||||
```python
|
||||
class IndieAuthError(Exception):
|
||||
"""Base exception for IndieAuth errors."""
|
||||
pass
|
||||
|
||||
class InvalidClientError(IndieAuthError):
|
||||
"""Raised when client authentication fails."""
|
||||
pass
|
||||
|
||||
class InvalidTokenError(IndieAuthError):
|
||||
"""Raised when token validation fails."""
|
||||
pass
|
||||
```
|
||||
|
||||
### Error Handling Pattern
|
||||
```python
|
||||
# Good - Specific exception handling
|
||||
try:
|
||||
token = validate_token(bearer_token)
|
||||
except InvalidTokenError as e:
|
||||
logger.warning(f"Token validation failed: {e}")
|
||||
return error_response(401, "invalid_token")
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error during validation: {e}")
|
||||
return error_response(500, "internal_error")
|
||||
|
||||
# Bad - Catching all exceptions
|
||||
try:
|
||||
token = validate_token(bearer_token)
|
||||
except: # Never use bare except
|
||||
return error_response(400, "error")
|
||||
```
|
||||
|
||||
## Logging
|
||||
|
||||
Use the standard `logging` module:
|
||||
|
||||
```python
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class TokenService:
|
||||
def create_token(self, client_id: str) -> str:
|
||||
logger.debug(f"Creating token for client: {client_id}")
|
||||
token = self._generate_token()
|
||||
logger.info(f"Token created for client: {client_id}")
|
||||
return token
|
||||
```
|
||||
|
||||
### Logging Levels
|
||||
- **DEBUG**: Detailed diagnostic information
|
||||
- **INFO**: General informational messages
|
||||
- **WARNING**: Warning messages for potentially harmful situations
|
||||
- **ERROR**: Error messages for failures
|
||||
- **CRITICAL**: Critical problems that require immediate attention
|
||||
|
||||
### Sensitive Data
|
||||
Never log sensitive data:
|
||||
```python
|
||||
# Bad
|
||||
logger.info(f"User logged in with password: {password}")
|
||||
logger.debug(f"Generated token: {access_token}")
|
||||
|
||||
# Good
|
||||
logger.info(f"User logged in: {user_id}")
|
||||
logger.debug(f"Token generated for client: {client_id}")
|
||||
```
|
||||
|
||||
## Configuration Management
|
||||
|
||||
Use environment variables for configuration:
|
||||
|
||||
```python
|
||||
# config.py
|
||||
import os
|
||||
from typing import Optional
|
||||
|
||||
class Config:
|
||||
"""Application configuration."""
|
||||
|
||||
# Required settings
|
||||
SECRET_KEY: str = os.environ["INDIEAUTH_SECRET_KEY"]
|
||||
DATABASE_URL: str = os.environ["INDIEAUTH_DATABASE_URL"]
|
||||
|
||||
# Optional settings with defaults
|
||||
TOKEN_EXPIRY: int = int(os.getenv("INDIEAUTH_TOKEN_EXPIRY", "3600"))
|
||||
RATE_LIMIT: int = int(os.getenv("INDIEAUTH_RATE_LIMIT", "100"))
|
||||
DEBUG: bool = os.getenv("INDIEAUTH_DEBUG", "false").lower() == "true"
|
||||
|
||||
@classmethod
|
||||
def validate(cls) -> None:
|
||||
"""Validate configuration on startup."""
|
||||
if not cls.SECRET_KEY:
|
||||
raise ValueError("INDIEAUTH_SECRET_KEY must be set")
|
||||
if len(cls.SECRET_KEY) < 32:
|
||||
raise ValueError("INDIEAUTH_SECRET_KEY must be at least 32 characters")
|
||||
```
|
||||
|
||||
## Dependency Management
|
||||
|
||||
### Requirements Files
|
||||
```
|
||||
requirements.txt # Production dependencies only
|
||||
requirements-dev.txt # Development dependencies (includes requirements.txt)
|
||||
requirements-test.txt # Test dependencies (includes requirements.txt)
|
||||
```
|
||||
|
||||
### Dependency Principles
|
||||
- Pin exact versions in requirements.txt
|
||||
- Minimize dependencies - prefer standard library
|
||||
- Audit dependencies for security vulnerabilities
|
||||
- Document why each dependency is needed
|
||||
|
||||
## Security Practices
|
||||
|
||||
### Input Validation
|
||||
Always validate and sanitize input:
|
||||
```python
|
||||
from urllib.parse import urlparse
|
||||
|
||||
def validate_redirect_uri(uri: str) -> bool:
|
||||
"""Validate that redirect URI is safe."""
|
||||
parsed = urlparse(uri)
|
||||
|
||||
# Must be absolute URI
|
||||
if not parsed.scheme or not parsed.netloc:
|
||||
return False
|
||||
|
||||
# Must be HTTPS in production
|
||||
if not DEBUG and parsed.scheme != "https":
|
||||
return False
|
||||
|
||||
# Prevent open redirects
|
||||
if parsed.netloc in BLACKLISTED_DOMAINS:
|
||||
return False
|
||||
|
||||
return True
|
||||
```
|
||||
|
||||
### Secrets Management
|
||||
```python
|
||||
import secrets
|
||||
|
||||
def generate_token() -> str:
|
||||
"""Generate cryptographically secure token."""
|
||||
return secrets.token_urlsafe(32)
|
||||
|
||||
def constant_time_compare(a: str, b: str) -> bool:
|
||||
"""Compare strings in constant time to prevent timing attacks."""
|
||||
return secrets.compare_digest(a, b)
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Async/Await
|
||||
Use async for I/O operations when beneficial:
|
||||
```python
|
||||
async def verify_client(client_id: str) -> Optional[Client]:
|
||||
"""Verify client exists and is valid."""
|
||||
client = await db.get_client(client_id)
|
||||
if client and not client.is_revoked:
|
||||
return client
|
||||
return None
|
||||
```
|
||||
|
||||
### Caching
|
||||
Cache expensive operations appropriately:
|
||||
```python
|
||||
from functools import lru_cache
|
||||
|
||||
@lru_cache(maxsize=128)
|
||||
def get_client_metadata(client_id: str) -> dict:
|
||||
"""Fetch and cache client metadata."""
|
||||
# Expensive operation
|
||||
return fetch_client_metadata(client_id)
|
||||
```
|
||||
|
||||
## Module Documentation
|
||||
|
||||
Each module should have a header docstring:
|
||||
```python
|
||||
"""
|
||||
Authorization endpoint implementation.
|
||||
|
||||
This module handles the OAuth 2.0 authorization endpoint as specified
|
||||
in the IndieAuth specification. It processes authorization requests,
|
||||
validates client information, and generates authorization codes.
|
||||
"""
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
### When to Comment
|
||||
- Complex algorithms or business logic
|
||||
- Workarounds or non-obvious solutions
|
||||
- TODO items with issue references
|
||||
- Security-critical code sections
|
||||
|
||||
### Comment Style
|
||||
```python
|
||||
# Good comments explain WHY, not WHAT
|
||||
|
||||
# Bad - Explains what the code does
|
||||
counter = counter + 1 # Increment counter
|
||||
|
||||
# Good - Explains why
|
||||
counter = counter + 1 # Track attempts for rate limiting
|
||||
|
||||
# Security-critical sections need extra attention
|
||||
# SECURITY: Validate redirect_uri to prevent open redirect attacks
|
||||
# See: https://owasp.org/www-project-web-security-testing-guide/
|
||||
if not validate_redirect_uri(redirect_uri):
|
||||
raise SecurityError("Invalid redirect URI")
|
||||
```
|
||||
|
||||
## Code Organization Principles
|
||||
|
||||
1. **Single Responsibility**: Each module/class/function does one thing
|
||||
2. **Dependency Injection**: Pass dependencies, don't hard-code them
|
||||
3. **Composition over Inheritance**: Prefer composition for code reuse
|
||||
4. **Fail Fast**: Validate input early and fail with clear errors
|
||||
5. **Explicit over Implicit**: Clear interfaces over magic behavior
|
||||
350
docs/standards/development-environment.md
Normal file
350
docs/standards/development-environment.md
Normal file
@@ -0,0 +1,350 @@
|
||||
# Development Environment Standard
|
||||
|
||||
## Overview
|
||||
|
||||
This document defines the standard development environment setup and workflow for the IndieAuth server project. We use **uv** for Python virtual environment management and package installation, following a direct execution model that eliminates the need for environment activation.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### System Requirements
|
||||
- **Python**: 3.10 or higher
|
||||
- **Git**: 2.25 or higher
|
||||
- **SQLite**: 3.35 or higher (usually included with Python)
|
||||
|
||||
### Installing uv
|
||||
|
||||
uv is available for all major platforms. Install using one of these methods:
|
||||
|
||||
**Linux/macOS (recommended):**
|
||||
```bash
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
```
|
||||
|
||||
**Using pip (alternative):**
|
||||
```bash
|
||||
pip install uv
|
||||
```
|
||||
|
||||
**Using Homebrew (macOS):**
|
||||
```bash
|
||||
brew install uv
|
||||
```
|
||||
|
||||
After installation, verify:
|
||||
```bash
|
||||
uv --version
|
||||
```
|
||||
|
||||
## Project Setup
|
||||
|
||||
### Initial Setup (First Time)
|
||||
|
||||
1. **Clone the repository:**
|
||||
```bash
|
||||
git clone <repository-url> gondulf-indieauth
|
||||
cd gondulf-indieauth
|
||||
```
|
||||
|
||||
2. **Create virtual environment with uv:**
|
||||
```bash
|
||||
uv venv
|
||||
```
|
||||
This creates a `.venv` directory in the project root.
|
||||
|
||||
3. **Install dependencies:**
|
||||
```bash
|
||||
uv pip sync requirements.txt
|
||||
```
|
||||
Or if using pyproject.toml:
|
||||
```bash
|
||||
uv pip install -e .
|
||||
```
|
||||
|
||||
### Daily Development Workflow
|
||||
|
||||
**IMPORTANT**: You do NOT need to activate the virtual environment. Use `uv run` to execute commands within the environment context.
|
||||
|
||||
## Using uv Direct Execution
|
||||
|
||||
### Core Concept
|
||||
Instead of activating/deactivating virtual environments, we use uv's direct execution commands. This approach:
|
||||
- Eliminates activation state confusion
|
||||
- Works consistently across all shells
|
||||
- Makes commands explicit and clear
|
||||
- Simplifies CI/CD pipelines
|
||||
|
||||
### Common Development Commands
|
||||
|
||||
**Running Python scripts:**
|
||||
```bash
|
||||
# Instead of: python script.py
|
||||
uv run python script.py
|
||||
```
|
||||
|
||||
**Starting the development server:**
|
||||
```bash
|
||||
# Instead of: python -m uvicorn main:app --reload
|
||||
uv run uvicorn main:app --reload
|
||||
```
|
||||
|
||||
**Running tests:**
|
||||
```bash
|
||||
# Instead of: pytest
|
||||
uv run pytest
|
||||
|
||||
# With coverage:
|
||||
uv run pytest --cov=src --cov-report=term-missing
|
||||
```
|
||||
|
||||
**Interactive Python shell:**
|
||||
```bash
|
||||
# Instead of: python
|
||||
uv run python
|
||||
```
|
||||
|
||||
**Running linters and formatters:**
|
||||
```bash
|
||||
# Instead of: ruff check .
|
||||
uv run ruff check .
|
||||
|
||||
# Format code:
|
||||
uv run ruff format .
|
||||
```
|
||||
|
||||
### Package Management
|
||||
|
||||
**Installing packages:**
|
||||
```bash
|
||||
# Instead of: pip install package
|
||||
uv pip install package
|
||||
|
||||
# Install with extras:
|
||||
uv pip install "package[extra]"
|
||||
|
||||
# Install development dependencies:
|
||||
uv pip install -r requirements-dev.txt
|
||||
```
|
||||
|
||||
**Upgrading packages:**
|
||||
```bash
|
||||
# Instead of: pip install --upgrade package
|
||||
uv pip install --upgrade package
|
||||
```
|
||||
|
||||
**Listing installed packages:**
|
||||
```bash
|
||||
# Instead of: pip list
|
||||
uv pip list
|
||||
```
|
||||
|
||||
**Freezing dependencies:**
|
||||
```bash
|
||||
# Instead of: pip freeze > requirements.txt
|
||||
uv pip freeze > requirements.txt
|
||||
```
|
||||
|
||||
**Syncing with lock file:**
|
||||
```bash
|
||||
# Ensures exact versions from lock file:
|
||||
uv pip sync requirements.txt
|
||||
```
|
||||
|
||||
## IDE Configuration
|
||||
|
||||
### VS Code
|
||||
|
||||
1. Open VS Code settings (`.vscode/settings.json`)
|
||||
2. Configure Python interpreter:
|
||||
```json
|
||||
{
|
||||
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
|
||||
"python.terminal.activateEnvironment": false
|
||||
}
|
||||
```
|
||||
|
||||
Setting `activateEnvironment` to false prevents VS Code from auto-activating, maintaining our direct execution model.
|
||||
|
||||
### PyCharm
|
||||
|
||||
1. Go to Settings → Project → Python Interpreter
|
||||
2. Click gear icon → Add
|
||||
3. Choose "Existing environment"
|
||||
4. Browse to: `<project-root>/.venv/bin/python`
|
||||
5. Uncheck "Activate virtualenv" in terminal settings
|
||||
|
||||
### Other IDEs
|
||||
|
||||
Point the interpreter to `.venv/bin/python` and disable automatic activation if the option exists.
|
||||
|
||||
## Common Workflows
|
||||
|
||||
### Starting a New Feature
|
||||
|
||||
1. **Ensure dependencies are up to date:**
|
||||
```bash
|
||||
uv pip sync requirements.txt
|
||||
```
|
||||
|
||||
2. **Create feature branch:**
|
||||
```bash
|
||||
git checkout -b feature/your-feature-name
|
||||
```
|
||||
|
||||
3. **Run tests to ensure clean state:**
|
||||
```bash
|
||||
uv run pytest
|
||||
```
|
||||
|
||||
### Adding a New Dependency
|
||||
|
||||
1. **Install the package:**
|
||||
```bash
|
||||
uv pip install new-package
|
||||
```
|
||||
|
||||
2. **Update requirements file:**
|
||||
```bash
|
||||
uv pip freeze > requirements.txt
|
||||
```
|
||||
|
||||
3. **Commit both changes:**
|
||||
```bash
|
||||
git add requirements.txt
|
||||
git commit -m "Add new-package to dependencies"
|
||||
```
|
||||
|
||||
### Running the Application
|
||||
|
||||
**Development mode with auto-reload:**
|
||||
```bash
|
||||
uv run uvicorn src.main:app --reload --host 127.0.0.1 --port 8000
|
||||
```
|
||||
|
||||
**Production-like mode:**
|
||||
```bash
|
||||
uv run uvicorn src.main:app --host 0.0.0.0 --port 8000
|
||||
```
|
||||
|
||||
### Running Tests
|
||||
|
||||
**All tests:**
|
||||
```bash
|
||||
uv run pytest
|
||||
```
|
||||
|
||||
**Specific test file:**
|
||||
```bash
|
||||
uv run pytest tests/test_auth.py
|
||||
```
|
||||
|
||||
**With coverage report:**
|
||||
```bash
|
||||
uv run pytest --cov=src --cov-report=html --cov-report=term-missing
|
||||
```
|
||||
|
||||
**Watch mode (requires pytest-watch):**
|
||||
```bash
|
||||
uv run ptw
|
||||
```
|
||||
|
||||
### Code Quality Checks
|
||||
|
||||
**Run all checks (before committing):**
|
||||
```bash
|
||||
# Linting
|
||||
uv run ruff check .
|
||||
|
||||
# Type checking
|
||||
uv run mypy src
|
||||
|
||||
# Security scanning
|
||||
uv run bandit -r src
|
||||
|
||||
# Test coverage
|
||||
uv run pytest --cov=src --cov-report=term-missing
|
||||
```
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
uv commands work seamlessly in CI/CD pipelines without activation:
|
||||
|
||||
**GitHub Actions example:**
|
||||
```yaml
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Install uv
|
||||
run: pip install uv
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
uv venv
|
||||
uv pip sync requirements.txt
|
||||
|
||||
- name: Run tests
|
||||
run: uv run pytest --cov=src
|
||||
|
||||
- name: Run linting
|
||||
run: uv run ruff check .
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Command not found errors
|
||||
**Problem**: Running `python` or `pytest` directly doesn't work.
|
||||
**Solution**: Always prefix with `uv run`: `uv run python` or `uv run pytest`
|
||||
|
||||
### Package installation issues
|
||||
**Problem**: Package conflicts or resolution errors.
|
||||
**Solution**: Clear the cache and reinstall:
|
||||
```bash
|
||||
uv cache clean
|
||||
uv pip sync requirements.txt
|
||||
```
|
||||
|
||||
### IDE not recognizing packages
|
||||
**Problem**: IDE shows import errors despite packages being installed.
|
||||
**Solution**: Ensure IDE interpreter points to `.venv/bin/python`, not system Python.
|
||||
|
||||
### Different behavior between local and CI
|
||||
**Problem**: Tests pass locally but fail in CI.
|
||||
**Solution**: Use `uv pip sync` instead of `uv pip install` to ensure exact versions.
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Never commit `.venv/`** - It's already in `.gitignore`
|
||||
2. **Always use `uv run`** for command execution
|
||||
3. **Keep requirements.txt updated** when adding/removing packages
|
||||
4. **Use `uv pip sync`** for reproducible environments
|
||||
5. **Document any special setup** in project README
|
||||
6. **Avoid activation scripts** - Use direct execution instead
|
||||
|
||||
## Benefits of This Approach
|
||||
|
||||
1. **Explicit Context**: Every command clearly shows it's running in the project environment
|
||||
2. **No Activation State**: Eliminates "which environment am I in?" confusion
|
||||
3. **Shell Agnostic**: Same commands work in bash, zsh, fish, PowerShell, cmd
|
||||
4. **CI/CD Friendly**: Commands are identical in local and automated environments
|
||||
5. **Faster Execution**: uv's Rust implementation provides superior performance
|
||||
6. **Simpler Onboarding**: New developers don't need to learn activation patterns
|
||||
|
||||
## Quick Reference Card
|
||||
|
||||
```bash
|
||||
# Setup (once)
|
||||
uv venv
|
||||
uv pip sync requirements.txt
|
||||
|
||||
# Daily commands
|
||||
uv run python script.py # Run a script
|
||||
uv run uvicorn src.main:app # Start server
|
||||
uv run pytest # Run tests
|
||||
uv run ruff check . # Lint code
|
||||
uv pip install package # Add package
|
||||
uv pip list # List packages
|
||||
uv run python # Interactive shell
|
||||
```
|
||||
|
||||
Remember: When in doubt, prefix with `uv run` for Python commands and `uv pip` for package management.
|
||||
149
docs/standards/git.md
Normal file
149
docs/standards/git.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Git Workflow Standard
|
||||
|
||||
## Overview
|
||||
|
||||
This project uses trunk-based development for simplicity and continuous integration.
|
||||
|
||||
## Branch Strategy
|
||||
|
||||
### Main Branch
|
||||
- `main` - The primary development branch
|
||||
- Always in a deployable state
|
||||
- Protected branch requiring review before merge
|
||||
- All commits must pass CI/CD checks
|
||||
|
||||
### Feature Development
|
||||
- Work directly on `main` for small, low-risk changes
|
||||
- Use short-lived feature branches for larger changes
|
||||
- Feature branches live maximum 2 days before merging
|
||||
|
||||
### Branch Naming (When Used)
|
||||
- `fix/brief-description` - Bug fixes
|
||||
- `feature/brief-description` - New features
|
||||
- `refactor/brief-description` - Code refactoring
|
||||
- `docs/brief-description` - Documentation only
|
||||
|
||||
Examples:
|
||||
- `feature/client-registration`
|
||||
- `fix/token-expiration`
|
||||
- `docs/api-endpoints`
|
||||
|
||||
## Commit Message Format
|
||||
|
||||
Follow Conventional Commits specification:
|
||||
|
||||
```
|
||||
<type>(<scope>): <subject>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer(s)]
|
||||
```
|
||||
|
||||
### Types
|
||||
- `feat`: New feature
|
||||
- `fix`: Bug fix
|
||||
- `docs`: Documentation only changes
|
||||
- `style`: Code style changes (formatting, missing semicolons, etc)
|
||||
- `refactor`: Code change that neither fixes a bug nor adds a feature
|
||||
- `perf`: Performance improvements
|
||||
- `test`: Adding or fixing tests
|
||||
- `chore`: Changes to build process or auxiliary tools
|
||||
|
||||
### Scope (Optional)
|
||||
Component or module affected:
|
||||
- `auth`: Authorization endpoint
|
||||
- `token`: Token endpoint
|
||||
- `client`: Client registration
|
||||
- `admin`: Administrative features
|
||||
- `db`: Database changes
|
||||
- `config`: Configuration changes
|
||||
|
||||
### Examples
|
||||
```
|
||||
feat(client): add self-registration endpoint
|
||||
|
||||
Implements dynamic client registration according to
|
||||
RFC 7591 with rate limiting to prevent abuse.
|
||||
|
||||
Closes #12
|
||||
```
|
||||
|
||||
```
|
||||
fix(token): correct expiration time calculation
|
||||
```
|
||||
|
||||
```
|
||||
docs: update README with installation instructions
|
||||
```
|
||||
|
||||
## Pull Request Process (When Using Branches)
|
||||
|
||||
1. Create branch from latest `main`
|
||||
2. Make changes with clear, atomic commits
|
||||
3. Ensure all tests pass locally
|
||||
4. Push branch and open pull request
|
||||
5. Ensure CI passes
|
||||
6. Request review if required
|
||||
7. Merge using "Squash and merge" for clean history
|
||||
|
||||
## Merge Strategy
|
||||
|
||||
- **Squash and merge** for feature branches (maintains clean history)
|
||||
- **Fast-forward only** for direct commits to main
|
||||
- Never use merge commits (no merge bubbles)
|
||||
|
||||
## Code Review Requirements
|
||||
|
||||
All code must be reviewed before merging to `main`:
|
||||
- Self-review checklist:
|
||||
- Tests pass
|
||||
- Documentation updated
|
||||
- No commented-out code
|
||||
- No debug statements
|
||||
- Follows coding standards
|
||||
- Peer review for significant changes
|
||||
|
||||
## Git Hooks (Recommended)
|
||||
|
||||
### Pre-commit
|
||||
- Run linter (e.g., `flake8` for Python)
|
||||
- Run formatter (e.g., `black` for Python)
|
||||
- Check for secrets/credentials
|
||||
|
||||
### Pre-push
|
||||
- Run test suite
|
||||
- Verify no large files
|
||||
|
||||
## Release Process
|
||||
|
||||
1. Ensure all tests pass on `main`
|
||||
2. Update version in appropriate files
|
||||
3. Create release commit: `chore: release v{version}`
|
||||
4. Tag the commit: `git tag -a v{version} -m "Release v{version}"`
|
||||
5. Push tag: `git push origin v{version}`
|
||||
6. Create GitHub release with release notes
|
||||
|
||||
## Rewriting History
|
||||
|
||||
- **Never** rewrite history on `main`
|
||||
- Only use `git rebase` on local branches before pushing
|
||||
- Use `git commit --amend` only for unpushed commits
|
||||
|
||||
## Large Files
|
||||
|
||||
- Use `.gitignore` to exclude generated files, dependencies, and build artifacts
|
||||
- Never commit:
|
||||
- Binary files (unless absolutely necessary)
|
||||
- Credentials or secrets
|
||||
- Local configuration files
|
||||
- IDE-specific files
|
||||
- Log files
|
||||
- Database files
|
||||
|
||||
## Repository Maintenance
|
||||
|
||||
- Keep repository size under 100MB
|
||||
- Regularly clean up merged branches
|
||||
- Archive old issues and pull requests
|
||||
- Maintain clear README and CONTRIBUTING files
|
||||
243
docs/standards/testing.md
Normal file
243
docs/standards/testing.md
Normal file
@@ -0,0 +1,243 @@
|
||||
# Testing Standard
|
||||
|
||||
## Overview
|
||||
|
||||
Testing is mandatory for all code. This project maintains high quality through comprehensive automated testing at multiple levels.
|
||||
|
||||
## Testing Philosophy
|
||||
|
||||
- Write tests first when possible (TDD approach)
|
||||
- Test behavior, not implementation
|
||||
- Keep tests simple and focused
|
||||
- Fast tests are better tests
|
||||
- Clear test names that describe what is being tested
|
||||
|
||||
## Test Coverage Requirements
|
||||
|
||||
### Minimum Coverage
|
||||
- Overall: 80% code coverage minimum
|
||||
- Critical paths (auth, token, security): 95% coverage
|
||||
- New code: 90% coverage required
|
||||
|
||||
### Coverage Exclusions
|
||||
Acceptable to exclude from coverage:
|
||||
- Logging statements
|
||||
- Debug utilities
|
||||
- Main entry points (if trivial)
|
||||
- Third-party integration boilerplate
|
||||
|
||||
## Testing Pyramid
|
||||
|
||||
### Unit Tests (70% of tests)
|
||||
- Test individual functions and methods
|
||||
- No external dependencies (use mocks)
|
||||
- Should run in milliseconds
|
||||
- Located in `tests/unit/`
|
||||
|
||||
### Integration Tests (20% of tests)
|
||||
- Test component interactions
|
||||
- May use test database
|
||||
- Test API endpoints with real HTTP requests
|
||||
- Located in `tests/integration/`
|
||||
|
||||
### End-to-End Tests (10% of tests)
|
||||
- Test complete IndieAuth flows
|
||||
- Verify compliance with W3C specification
|
||||
- Include client registration and authentication flows
|
||||
- Located in `tests/e2e/`
|
||||
|
||||
## Test Structure
|
||||
|
||||
### Test File Organization
|
||||
```
|
||||
tests/
|
||||
├── unit/
|
||||
│ ├── test_auth.py
|
||||
│ ├── test_token.py
|
||||
│ └── test_client.py
|
||||
├── integration/
|
||||
│ ├── test_auth_endpoint.py
|
||||
│ ├── test_token_endpoint.py
|
||||
│ └── test_client_registration.py
|
||||
├── e2e/
|
||||
│ ├── test_full_auth_flow.py
|
||||
│ └── test_client_self_registration.py
|
||||
├── fixtures/
|
||||
│ └── test_data.py
|
||||
└── conftest.py # pytest configuration
|
||||
```
|
||||
|
||||
### Test Naming Convention
|
||||
- Test files: `test_*.py`
|
||||
- Test classes: `Test*`
|
||||
- Test methods: `test_*`
|
||||
|
||||
Examples:
|
||||
```python
|
||||
# Good test names
|
||||
def test_token_expires_after_configured_duration():
|
||||
def test_client_registration_requires_redirect_uri():
|
||||
def test_authorization_code_is_single_use():
|
||||
|
||||
# Bad test names
|
||||
def test_auth(): # Too vague
|
||||
def test_1(): # Meaningless
|
||||
```
|
||||
|
||||
## Python Testing Tools
|
||||
|
||||
### Core Testing Framework
|
||||
- **pytest**: Primary testing framework
|
||||
- Use fixtures for test setup
|
||||
- Use parametrize for testing multiple cases
|
||||
- Use markers for test categorization
|
||||
|
||||
### Required Testing Libraries
|
||||
```python
|
||||
# requirements-test.txt
|
||||
pytest>=7.0.0
|
||||
pytest-cov>=4.0.0 # Coverage reporting
|
||||
pytest-asyncio>=0.20.0 # Async test support
|
||||
pytest-mock>=3.10.0 # Mocking utilities
|
||||
freezegun>=1.2.0 # Time mocking
|
||||
factory-boy>=3.2.0 # Test data factories
|
||||
responses>=0.22.0 # HTTP response mocking
|
||||
```
|
||||
|
||||
### Test Fixtures
|
||||
```python
|
||||
# Example fixture structure
|
||||
@pytest.fixture
|
||||
def auth_client():
|
||||
"""Returns authenticated test client."""
|
||||
|
||||
@pytest.fixture
|
||||
def test_user():
|
||||
"""Returns test user with domain."""
|
||||
|
||||
@pytest.fixture
|
||||
def registered_client():
|
||||
"""Returns pre-registered OAuth client."""
|
||||
```
|
||||
|
||||
## Writing Tests
|
||||
|
||||
### Test Structure (AAA Pattern)
|
||||
```python
|
||||
def test_authorization_code_exchange():
|
||||
# Arrange - Set up test data and conditions
|
||||
code = generate_auth_code()
|
||||
client = create_test_client()
|
||||
|
||||
# Act - Execute the behavior being tested
|
||||
token = exchange_code_for_token(code, client)
|
||||
|
||||
# Assert - Verify the outcome
|
||||
assert token.is_valid()
|
||||
assert token.client_id == client.id
|
||||
```
|
||||
|
||||
### Mocking Guidelines
|
||||
- Mock external services (HTTP calls, databases in unit tests)
|
||||
- Don't mock what you're testing
|
||||
- Prefer dependency injection over patching
|
||||
|
||||
### Testing IndieAuth Compliance
|
||||
Special test suite for W3C specification compliance:
|
||||
```python
|
||||
class TestIndieAuthCompliance:
|
||||
"""Tests that verify W3C IndieAuth specification compliance."""
|
||||
|
||||
def test_authorization_endpoint_supports_response_type_code(self):
|
||||
def test_token_endpoint_requires_code_verifier_with_pkce(self):
|
||||
def test_client_id_must_be_valid_url(self):
|
||||
```
|
||||
|
||||
## Test Execution
|
||||
|
||||
### Running Tests
|
||||
```bash
|
||||
# Run all tests
|
||||
pytest
|
||||
|
||||
# Run with coverage
|
||||
pytest --cov=indieauth --cov-report=html
|
||||
|
||||
# Run specific test level
|
||||
pytest tests/unit/
|
||||
pytest tests/integration/
|
||||
pytest tests/e2e/
|
||||
|
||||
# Run tests matching pattern
|
||||
pytest -k "test_token"
|
||||
|
||||
# Run with verbose output
|
||||
pytest -vv
|
||||
```
|
||||
|
||||
### Continuous Integration
|
||||
All tests must pass before merging:
|
||||
```yaml
|
||||
# Example CI configuration
|
||||
test:
|
||||
- pytest tests/unit/ --cov=indieauth
|
||||
- pytest tests/integration/
|
||||
- pytest tests/e2e/
|
||||
- coverage report --fail-under=80
|
||||
```
|
||||
|
||||
## Performance Testing
|
||||
|
||||
### Response Time Requirements
|
||||
- Authorization endpoint: < 200ms
|
||||
- Token endpoint: < 100ms
|
||||
- Client registration: < 500ms
|
||||
|
||||
### Load Testing
|
||||
- Support 100 concurrent authentications
|
||||
- Handle 1000 registered clients
|
||||
- Token validation: 10,000 requests/minute
|
||||
|
||||
## Security Testing
|
||||
|
||||
### Required Security Tests
|
||||
- Test for timing attacks in token validation
|
||||
- Test rate limiting on all endpoints
|
||||
- Test PKCE validation
|
||||
- Test redirect URI validation
|
||||
- Test for open redirect vulnerabilities
|
||||
- Test token entropy and uniqueness
|
||||
|
||||
## Test Data Management
|
||||
|
||||
### Test Database
|
||||
- Use in-memory SQLite for unit tests
|
||||
- Use PostgreSQL/MySQL for integration tests (same as production)
|
||||
- Reset database between test runs
|
||||
- Use transactions for test isolation
|
||||
|
||||
### Test Secrets
|
||||
- Never use production secrets in tests
|
||||
- Generate test keys and tokens dynamically
|
||||
- Use consistent test data for reproducibility
|
||||
|
||||
## Documentation of Tests
|
||||
|
||||
Each test should be self-documenting:
|
||||
```python
|
||||
def test_expired_token_is_rejected():
|
||||
"""
|
||||
Verify that tokens past their expiration time are rejected.
|
||||
|
||||
This prevents replay attacks and ensures tokens have limited lifetime
|
||||
as required by OAuth 2.0 security best practices.
|
||||
"""
|
||||
```
|
||||
|
||||
## Test Maintenance
|
||||
|
||||
- Review and update tests when requirements change
|
||||
- Remove obsolete tests
|
||||
- Refactor tests that become brittle
|
||||
- Keep test execution time under 1 minute for unit tests
|
||||
- Keep full test suite under 5 minutes
|
||||
80
docs/standards/versioning.md
Normal file
80
docs/standards/versioning.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Versioning Standard
|
||||
|
||||
## Overview
|
||||
|
||||
This project follows Semantic Versioning 2.0.0 (https://semver.org/) for all releases.
|
||||
|
||||
## Version Format
|
||||
|
||||
`MAJOR.MINOR.PATCH`
|
||||
|
||||
- **MAJOR**: Incremented for incompatible API changes or breaking changes to IndieAuth protocol implementation
|
||||
- **MINOR**: Incremented for new features added in a backward-compatible manner
|
||||
- **PATCH**: Incremented for backward-compatible bug fixes and documentation improvements
|
||||
|
||||
## Version Increments for IndieAuth Server
|
||||
|
||||
### MAJOR Version Changes (Breaking)
|
||||
- Changes that break compatibility with existing IndieAuth clients
|
||||
- Removal or modification of required IndieAuth endpoints
|
||||
- Changes to token formats that invalidate existing tokens
|
||||
- Database schema changes that require manual migration
|
||||
- Configuration format changes that are not backward-compatible
|
||||
- Removal of previously supported authentication methods
|
||||
|
||||
### MINOR Version Changes (Features)
|
||||
- Addition of new optional IndieAuth endpoints (e.g., metadata endpoint)
|
||||
- New client registration features
|
||||
- Additional authentication methods
|
||||
- Performance optimizations that don't change behavior
|
||||
- New administrative features
|
||||
- Support for additional IndieAuth extensions
|
||||
|
||||
### PATCH Version Changes (Fixes)
|
||||
- Bug fixes that don't change API behavior
|
||||
- Security patches that maintain compatibility
|
||||
- Documentation improvements
|
||||
- Internal refactoring without external changes
|
||||
- Dependency updates that don't affect functionality
|
||||
- Configuration option additions with sensible defaults
|
||||
|
||||
## Pre-release Versions
|
||||
|
||||
Pre-release versions may be denoted by appending:
|
||||
- `-alpha.N` for early development versions
|
||||
- `-beta.N` for feature-complete testing versions
|
||||
- `-rc.N` for release candidates
|
||||
|
||||
Example: `1.0.0-beta.2`
|
||||
|
||||
## Initial Release Target
|
||||
|
||||
`1.0.0` - First stable release with:
|
||||
- Full W3C IndieAuth specification compliance
|
||||
- Client self-registration capability
|
||||
- Production-ready security model
|
||||
|
||||
## Version Tagging
|
||||
|
||||
Git tags follow the format: `v{MAJOR}.{MINOR}.{PATCH}`
|
||||
|
||||
Example: `v1.0.0`, `v1.1.0-beta.1`
|
||||
|
||||
## Release Notes
|
||||
|
||||
Each release must include:
|
||||
- Version number and release date
|
||||
- Summary of changes categorized by type (Features, Fixes, Breaking Changes)
|
||||
- Migration instructions for breaking changes
|
||||
- Known issues if applicable
|
||||
- Contributors acknowledgment
|
||||
|
||||
## Version Support Policy
|
||||
|
||||
- Latest MAJOR version: Full support with bug fixes and security updates
|
||||
- Previous MAJOR version: Security updates only for 6 months after new MAJOR release
|
||||
- Older versions: No support
|
||||
|
||||
## Development Branch Versioning
|
||||
|
||||
The main branch always contains the next unreleased version with `-dev` suffix in any version files.
|
||||
Reference in New Issue
Block a user