Metadata-Version: 2.4
Name: altissimo-auth
Version: 1.1.0
Summary: Reusable, framework-agnostic authentication library for Altissimo projects.
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: auth,fastapi,django-ninja,firebase,google,oidc
Author: Altissimo HQ
Requires-Python: >=3.11
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: FastAPI
Classifier: Framework :: Django
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Typing :: Typed
Provides-Extra: all
Provides-Extra: fastapi
Provides-Extra: firebase
Provides-Extra: flask
Provides-Extra: google
Provides-Extra: jwt
Provides-Extra: ninja
Requires-Dist: django-ninja (>=1.0) ; extra == "all"
Requires-Dist: django-ninja (>=1.0) ; extra == "ninja"
Requires-Dist: email-validator (>=2.0)
Requires-Dist: fastapi (>=0.100.0) ; extra == "all"
Requires-Dist: fastapi (>=0.100.0) ; extra == "fastapi"
Requires-Dist: firebase-admin (>=6.0,<7.0) ; extra == "all"
Requires-Dist: firebase-admin (>=6.0,<7.0) ; extra == "firebase"
Requires-Dist: flask (>=2.0) ; extra == "all"
Requires-Dist: flask (>=2.0) ; extra == "flask"
Requires-Dist: google-auth (>=2.0,<3.0) ; extra == "all"
Requires-Dist: google-auth (>=2.0,<3.0) ; extra == "google"
Requires-Dist: google-auth-oauthlib (>=1.2) ; extra == "all"
Requires-Dist: google-auth-oauthlib (>=1.2) ; extra == "flask"
Requires-Dist: pydantic (>=2.0,<3.0)
Requires-Dist: pyjwt (>=2.8) ; extra == "all"
Requires-Dist: pyjwt (>=2.8) ; extra == "jwt"
Project-URL: Changelog, https://github.com/altissimo-hq/auth-providers-python/blob/main/CHANGELOG.md
Project-URL: Homepage, https://github.com/altissimo-hq/auth-providers-python
Project-URL: Issues, https://github.com/altissimo-hq/auth-providers-python/issues
Project-URL: Repository, https://github.com/altissimo-hq/auth-providers-python
Description-Content-Type: text/markdown

# altissimo-auth

[![CI](https://github.com/altissimo-hq/auth-providers-python/actions/workflows/ci.yml/badge.svg)](https://github.com/altissimo-hq/auth-providers-python/actions/workflows/ci.yml)
[![Python](https://img.shields.io/badge/python-3.11%20%7C%203.12%20%7C%203.13-blue)](https://www.python.org)
[![License](https://img.shields.io/badge/license-Apache%202.0-green)](LICENSE)
[![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff)

Reusable, framework-agnostic authentication library for Python applications.

## Features

- 🔑 **API Key** authentication with pluggable storage backends
- 🔥 **Firebase** ID token verification and user record resolution
- 🌐 **Google OAuth2** ID token verification
- 🛡️ **Google IAP** header extraction
- 🔒 **OIDC** service-to-service authentication with configurable caller policies
- 🪝 **Webhook** signature verification (Stripe included, extensible via protocol)
- ⚡ **FastAPI** adapter with `Depends()` wrappers and structured telemetry
- 🥷 **Django Ninja** adapter with native auth classes
- 🧪 **Flask** adapter for server-side Google OAuth2 Authorization Code flow
- 🌊 **Auth Cascade** for multi-method authentication fallback
- 🧅 **Layered Auth** for required gate + optional identity (BFF pattern)
- 📐 **Typed** — full type annotations with `py.typed` PEP 561 marker
- 🧪 **Testing stubs** — drop-in test helpers that eliminate ~100 lines of conftest boilerplate

## Requirements

- Python 3.11+

## Installation

Published on PyPI as [`altissimo-auth`](https://pypi.org/project/altissimo-auth/);
the import name is `altissimo.auth`.

```bash
pip install "altissimo-auth[fastapi]"        # core + FastAPI adapter
pip install "altissimo-auth[ninja]"          # core + Django Ninja adapter
pip install "altissimo-auth[flask]"          # core + Flask OAuth2 adapter
pip install "altissimo-auth[firebase]"       # core + Firebase provider
pip install "altissimo-auth[google]"         # core + Google OAuth2/OIDC providers
pip install "altissimo-auth[all]"            # everything
```

## Quick Start

### FastAPI

```python
from typing import Annotated

from fastapi import Depends, FastAPI

from altissimo.auth import FirebaseUser
from altissimo.auth.fastapi import Auth, configure
from altissimo.auth.service import AuthService

app = FastAPI()

# Configure once at startup
configure(AuthService(api_key_backend=my_backend))

# Route-level guard
@app.get("/items", dependencies=[Depends(Auth.validate_api_key)])
async def list_items():
    ...

# Parameter injection
@app.get("/me")
async def get_me(
    user: Annotated[FirebaseUser, Depends(Auth.validate_firebase_user)],
):
    return {"uid": user.uid, "email": user.email}
```

### Django Ninja

```python
from ninja import NinjaAPI

from altissimo.auth.cascade import AuthCascade
from altissimo.auth.ninja import ApiKeyAuth, FirebaseAuth, OIDCAuth, configure
from altissimo.auth.service import AuthService

api = NinjaAPI()

# Configure once at startup
configure(AuthService(api_key_backend=my_backend))

# Route-level guard
@api.get("/items", auth=ApiKeyAuth())
def list_items(request):
    ...

# Cascading Auth (Multi-Method Fallback)
cascade = AuthCascade([FirebaseAuth(), ApiKeyAuth()])

@api.get("/me", auth=cascade)
def get_me(request):
    user = request.auth  # Will be FirebaseUser or APIKeyRecord
    ...

# Layered Auth (Required Gate + Optional Identity)
from altissimo.auth.ninja import LayeredAuth

public_auth = LayeredAuth(
    gate=ApiKeyAuth(),        # MUST pass — 401 if it fails
    identity=FirebaseAuth(),  # OPTIONAL — enriches request.auth if present
)

@api.get("/communities", auth=public_auth)
def list_communities(request):
    user = request.auth       # FirebaseUser | None
    api_key = request.gate_auth  # APIKeyRecord (always available)
    ...
```

### Flask (Server-Side Google OAuth2)

```python
from flask import Flask, g
from altissimo.auth.flask import OAuth2FlowManager

app = Flask(__name__)
app.secret_key = "super-secret"

auth = OAuth2FlowManager(
    client_secrets_file="client_secret.json",
    scopes=["openid", "email", "profile"],
    redirect_uri_endpoint="oauth2_callback",
    hosted_domain="mycompany.com"  # Optional: restrict to domain
)

@app.route("/login")
def login():
    return auth.login_redirect()

@app.route("/oauth2_callback")
def oauth2_callback():
    user_info = auth.handle_callback()
    return f"Logged in as {user_info['email']}"

@app.route("/logout")
def logout():
    auth.logout()
    return "Logged out"

@app.route("/protected")
@auth.require_auth
def protected():
    return f"Hello, {g.user.email}"  # g.user is populated by require_auth
```

### Testing

```python
# conftest.py — runs before Django is configured
from altissimo.auth.testing import install_test_modules, StubApiKeyAuth

class MyApiKeyAuth(StubApiKeyAuth):
    def authenticate(self, request, key):
        api_key_id = key or request.GET.get("api_key")
        if not api_key_id:
            return None
        return MyBackend().get_key(api_key_id)

install_test_modules(api_key_auth_class=MyApiKeyAuth)

# Now configure Django and import your app normally...
```

### OIDC Service-to-Service

```python
from altissimo.auth.fastapi import Auth
from altissimo.auth.providers.oidc import OIDCPolicy

policy = OIDCPolicy(
    allowed_callers={
        "dev": ["sa@my-project-dev.iam.gserviceaccount.com"],
        "prod": ["sa@my-project-prod.iam.gserviceaccount.com"],
    },
    valid_audiences={
        "dev": ["https://api-dev.run.app"],
        "prod": ["https://api-prod.run.app"],
    },
    project_sa_suffix="@my-project-{env}.iam.gserviceaccount.com",
    team_domains=["mycompany.com"],
)

verify_caller = Auth.create_oidc_dependency(policy)

@app.post("/callback", dependencies=[Depends(verify_caller)])
async def handle_callback():
    ...
```

## Architecture

```text
altissimo.auth
├── core/           # Framework-agnostic models, exceptions, policies
├── providers/      # Token/key verification (API key, Firebase, Google, OIDC, IAP, webhooks)
├── service.py      # AuthService orchestration layer
├── fastapi/        # FastAPI Depends() wrappers
└── ninja/          # Django Ninja auth classes
```

### Provider → Service → Adapter Pattern

- **Providers** contain the actual auth logic (token verification, key lookup)
- **AuthService** wires providers + policies together
- **Adapters** are thin framework-specific wrappers that delegate to AuthService

### Pluggable Backends

API key storage and webhook verification are abstracted via Python Protocols:

```python
from altissimo.auth.providers import APIKeyBackend

class MyKeyBackend:
    """Implement the APIKeyBackend protocol with your storage."""

    def get_key(self, key_id: str) -> APIKeyRecord | None:
        return db.query(APIKey).filter_by(id=key_id).first()
```

## Development

```bash
# Install all dependencies
poetry sync

# Run tests
poetry run pytest

# Run tests with coverage
poetry run pytest --cov=altissimo --cov-report=term-missing

# Run linters
poetry run ruff check .
poetry run ruff format --check .
```

See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed development guidelines.

## Changelog

See [CHANGELOG.md](CHANGELOG.md) for version history.

## Security

For reporting security vulnerabilities, see [SECURITY.md](SECURITY.md).

## License

Apache License 2.0 — see [LICENSE](LICENSE) for details.

