Metadata-Version: 2.4
Name: pkg-auth
Version: 3.1.0
Summary: Clean-architecture auth core for multiple Python frameworks
Author-email: Fritill <info@fritill.ae>
License: MIT
Project-URL: Homepage, https://github.com/fritill-team/fri_pkg_auth
Project-URL: Repository, https://github.com/fritill-team/fri_pkg_auth
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.28.1
Requires-Dist: pyjwt[crypto]>=2.10.1
Requires-Dist: requests>=2.32.3
Provides-Extra: dev
Requires-Dist: pytest<9.0.0,>=8.0.0; extra == "dev"
Requires-Dist: pytest-asyncio<1.0.0,>=0.23.0; extra == "dev"
Requires-Dist: mypy<2.0.0,>=1.11.0; extra == "dev"
Requires-Dist: types-requests<3.0.0.0,>=2.32.0.0; extra == "dev"
Requires-Dist: types-PyJWT<2.0.0,>=1.7.0; extra == "dev"
Requires-Dist: testcontainers[postgres,redis]>=4.0; extra == "dev"
Provides-Extra: acl-sqlalchemy
Requires-Dist: sqlalchemy<3.0,>=2.0; extra == "acl-sqlalchemy"
Requires-Dist: asyncpg>=0.29; extra == "acl-sqlalchemy"
Requires-Dist: alembic>=1.13; extra == "acl-sqlalchemy"
Provides-Extra: acl-django
Requires-Dist: django>=4.2; extra == "acl-django"
Requires-Dist: psycopg[binary]>=3.1; extra == "acl-django"
Provides-Extra: cache-redis
Requires-Dist: redis>=5.0; extra == "cache-redis"
Provides-Extra: fastapi
Requires-Dist: fastapi>=0.115; extra == "fastapi"
Requires-Dist: starlette>=0.37; extra == "fastapi"
Provides-Extra: django
Requires-Dist: django>=4.2; extra == "django"
Provides-Extra: strawberry
Requires-Dist: strawberry-graphql>=0.255; extra == "strawberry"
Provides-Extra: all
Requires-Dist: django>=6.0; extra == "all"
Requires-Dist: fastapi>=0.115; extra == "all"
Requires-Dist: starlette>=0.37; extra == "all"
Requires-Dist: django>=4.2; extra == "all"
Requires-Dist: psycopg[binary]>=3.1; extra == "all"
Requires-Dist: strawberry-graphql>=0.255; extra == "all"
Requires-Dist: sqlalchemy<3.0,>=2.0; extra == "all"
Requires-Dist: asyncpg>=0.29; extra == "all"
Requires-Dist: alembic>=1.13; extra == "all"
Requires-Dist: redis>=5.0; extra == "all"

# pkg-auth

Clean-architecture **identity + ACL** for multi-framework Python services. Handles JWT authentication (via Keycloak) and database-backed authorization (users, organizations, roles, permissions, memberships) in a single package with first-class support for **FastAPI**, **Django**, and **Strawberry GraphQL**.

> **v3.0.0 is a breaking change.** The `permissions.is_platform` flag is replaced by a tri-state `visibility` enum (`platform_only`/`shared`/`tenant_only`); permission `description` is now a localized JSONB map; and a first-class **Service** model with a **default-deny service guard** is added. See [`docs/Upgrade-Service-Guard.md`](docs/Upgrade-Service-Guard.md) for the 3.0.0 upgrade guide. Earlier migrations: [`docs/MIGRATION_v1.md`](docs/MIGRATION_v1.md), [`docs/MIGRATION_v2.md`](docs/MIGRATION_v2.md).

## Install

```bash
# Core (identity only — no DB deps)
pip install pkg-auth

# With ACL + FastAPI (most common for fritill services)
pip install "pkg-auth[acl-sqlalchemy,fastapi]"

# With ACL + Django
pip install "pkg-auth[acl-django,django]"

# With optional Redis cache
pip install "pkg-auth[cache-redis]"

# Everything
pip install "pkg-auth[all]"
```

Available extras: `acl-sqlalchemy`, `acl-django`, `cache-redis`, `fastapi`, `django`, `strawberry`, `all`.

## Quickstart (FastAPI)

```python
from fastapi import Depends, FastAPI
from pkg_auth.authentication import IdentityContext
from pkg_auth.authorization import AuthContext
from pkg_auth.integrations.fastapi import (
    create_authentication,
    make_get_auth_context,
    require_permission,
)

# --- Wire authentication + authorization ---

auth = create_authentication(
    keycloak_base_url="https://auth.example.com",
    realm="fritill",
    audience="courses-service",
)

# Mode B (consumer — the common case): pass resolve_user_use_case.
# Mode A (source-of-truth): pass sync_user_use_case instead. Exactly
# one of the two is required; passing both raises ValueError.
get_auth_context = make_get_auth_context(
    get_identity=auth.get_identity,
    resolve_user_use_case=resolve_user,   # or: sync_user_use_case=sync_user (Mode A)
    resolve_use_case=resolve,
    organization_repo=org_repo,
)

app = FastAPI()

# --- Use in routes ---

@app.get("/courses/{id}")
async def get_course(
    id: str,
    bundle: tuple[IdentityContext, AuthContext] = Depends(
        require_permission("course:view", get_auth_context=get_auth_context)
    ),
):
    identity, auth_ctx = bundle
    return {"course_id": id, "roles": sorted(auth_ctx.role_names)}
```

See [`examples/itqadem_courses_app`](examples/itqadem_courses_app) for a complete working example.

## Features

- **Authentication** — Keycloak JWT validation producing an `IdentityContext`.
- **Authorization (ACL)** — database-backed users, organizations, roles, permissions, and memberships, resolved into a hot-path `AuthContext` (`role_names`, `perms`).
- **Permission visibility** — tri-state catalog visibility (`platform_only` / `shared` / `tenant_only`).
- **Localized descriptions** — permission descriptions stored as localized JSONB (`LocalizedText`, `ACL_DEFAULT_LOCALE`).
- **Services & service guard** — first-class `Service` / `OrganizationService` model with a **default-deny** service guard; the platform org bypasses it.
- **Mode A / Mode B** — be the source of truth for the `users` table, or consume a shared ACL read-only.
- **Integrations** — FastAPI, Django, and Strawberry GraphQL.
- **CLIs** — `keycloak-init-client`, `pkg-auth-sync-catalog`, `pkg-auth-sync-services`.

## Architecture

```
pkg_auth/
  authentication/             JWT validation → IdentityContext (identity only)
  authorization/              Full ACL (users, orgs, roles, perms, memberships)
    domain/                   Pure entities, ports (Protocol), exceptions
    application/use_cases/    Business logic (use cases)
    adapters/
      sqlalchemy/             Canonical schema + Alembic migrations (0001–0005) + repos
      django_orm/             Mirror models (managed=False) + repos
      cache/                  InMemoryTTLCache / RedisCache + decorator
  integrations/
    fastapi/                  Deps + require_permission + exception handlers
    django/                   Middleware + decorators
    strawberry/               Context getter + permission classes
  admin/                      Keycloak admin client (user provisioning)
```

**Layering rules**: domain has zero external imports; application imports only domain; adapters import their framework; integrations import everything.

## Documentation

- [Authorization model](docs/Authorization.md) — schema, permission catalog, roles, memberships, service guard
- [Upgrade to the Service Guard (3.0.0)](docs/Upgrade-Service-Guard.md)
- [Caching](docs/Caching.md) — InMemoryTTLCache, RedisCache, invalidation contract
- [FastAPI Integration](docs/FastAPI.md)
- [Django Integration](docs/Django.md)
- [Strawberry Integration](docs/Strawberry.md)
- [Keycloak Admin](docs/Keycloak-Admin.md)
- [Migration v1](docs/MIGRATION_v1.md) · [Migration v2](docs/MIGRATION_v2.md)
