Metadata-Version: 2.4
Name: neva-auth
Version: 0.1.0
Summary: Authentication & identity toolkit for the Neva framework.
Requires-Python: >=3.12
Requires-Dist: python-neva>=3.5.0
Provides-Extra: fastapi
Requires-Dist: neva-fastapi>=1.1.0; extra == 'fastapi'
Description-Content-Type: text/markdown

# neva-auth

Authentication & identity toolkit for the [Neva](https://pypi.org/project/python-neva/) framework — Laravel
philosophy, framework-agnostic core, composable so you adopt only the slice you
need.

> **Design:** see [`DESIGN.md`](./DESIGN.md) for the full architecture, contracts,
> schema, composability rules, v1 scope, and roadmap.

## Status

The **framework-agnostic core is complete and tested end to end** — token engine,
contracts, the extensibility model, security events, and the flows below — and is ready
to adopt as a **token-issuance backbone** (bring your own drivers). For what's planned
beyond today, see the roadmap in [`DESIGN.md`](./DESIGN.md).

**Flows.** `client_credentials` (M2M), `authorization_code` + **PKCE** (S256; public
*and* confidential clients), and `refresh_token` (rotation with reuse detection —
replaying a rotated token revokes the whole family) all run through `POST /oauth/token`;
`POST /oauth/introspect` validates. First-party **password login** (`POST /auth/login`,
over an agnostic `UserProvider`) issues tokens, and `GET /auth/me` resolves the user via
the `CurrentUser` dependency.

**Extensibility ([DESIGN §7.1](./DESIGN.md)).** Every pluggable axis with runtime
dispatch is a `StrategyResolver` registry — guards (`AuthManager`), authn methods
(`AuthnManager`), and OAuth grants (`GrantManager`) — so a consumer registers its own
driver under a name. The token **format** and **backend** are boot-time bindings chosen
by config; bring your own by implementing the protocol. The wire token need not carry
the `jti` (a format sets `ParsedToken.lookup_key`), so reference / split-token schemes
plug in unchanged.

**Events.** The engine emits `TokenIssued` / `TokenRevoked` / `TokenReuseDetected`; the
login flow emits `LoginSucceeded` / `LoginFailed`. Consumers project these onto their own
model for presence or audit — the toolkit ships no presence service (DESIGN §10).

The FastAPI layer lives in `neva.auth.http` behind the **`[fastapi]`** extra and is
mounted manually (`app.include_router(...)`), so neva-auth and neva-fastapi never require
each other — the bridge is opt-in.

## Modules

| Module | Provides |
| --- | --- |
| `neva.auth.core` | contracts, `AuthManager` (guards) + `AuthnManager` (methods), `Auth` facade, security events |
| `neva.auth.tokens` | `TokenEngine` + `TokenFormat`/`TokenBackend` contracts (opaque + SQL default), format-declared `lookup_key`, `family_id`-tracked rotation/reuse, family + subject revocation |
| `neva.auth.guards` | bearer `TokenGuard`; guard name→driver→provider resolution |
| `neva.auth.oauth` | client registry, PKCE code store, and the token-endpoint grants (`client_credentials`, `authorization_code`+PKCE, `refresh_token`) via `GrantManager` + `GrantResolver` |
| `neva.auth.http` | `/oauth/token`, `/oauth/introspect`, `/auth/login`, `/auth/me`, `Authenticated`/`CurrentUser`/`require_scope` deps (`[fastapi]`) |

Each module ships its own `ServiceProvider` and (where relevant) its own
migrations, so a consumer wires only what they list in `config/providers.py`.
The HTTP layer lives in `neva.auth.http` (the `[fastapi]` extra) — never in
neva-fastapi, which stays unaware of auth.

## Develop

```bash
uv sync
poe lint     # ruff check
poe fmt      # ruff format
poe tc       # mypy
poe test     # pytest
```

`asyncio_mode = "auto"` is set, so async tests need no `@pytest.mark.asyncio`.

## Contributing

**Commits** follow [Conventional Commits](https://www.conventionalcommits.org/)
with [gitmoji](https://gitmoji.dev/) prefixes, enforced by
[`cz_gitmoji`](https://github.com/ljnsn/cz-conventional-gitmoji). Commitizen is
a dev dependency — run `cz commit` for the guided wizard, or format manually as
`:gitmoji: type(scope): subject`.

**Releases** are cut per-repo with commitizen from the repo root:

```bash
cz bump                          # bump version in pyproject, write CHANGELOG, tag v<version>
git push --follow-tags origin main
uv build && uv publish           # build + publish the wheel/sdist
```

`cz bump` derives the level from the commits since the last tag, updates
`CHANGELOG.md`, and runs `scripts/retag-with-changelog.sh` to rewrite the new
tag with the rendered changelog as its annotation.
