Metadata-Version: 2.4
Name: phzyx-auth
Version: 0.1.0
Summary: Phzyx Auth — secure-by-default identity, sessions, JWT, API keys, RBAC (Forge battery)
Project-URL: Homepage, https://phzyx.xyz
Project-URL: Repository, https://github.com/phzyxyz/forge
Author: Athul Nandaswaroop
License-Expression: MIT
Requires-Python: >=3.11
Requires-Dist: argon2-cffi>=23
Requires-Dist: phzyx-forge<0.2,>=0.1.0
Requires-Dist: pydantic>=2
Requires-Dist: pyjwt>=2.8
Provides-Extra: fastapi
Requires-Dist: fastapi>=0.115; extra == 'fastapi'
Provides-Extra: litestar
Requires-Dist: litestar>=2.12; extra == 'litestar'
Provides-Extra: mfa
Requires-Dist: pyotp>=2.9; extra == 'mfa'
Provides-Extra: oauth
Requires-Dist: authlib>=1.3; extra == 'oauth'
Requires-Dist: httpx>=0.27; extra == 'oauth'
Provides-Extra: redis
Requires-Dist: redis>=5; extra == 'redis'
Provides-Extra: webauthn
Requires-Dist: webauthn>=2.0; extra == 'webauthn'
Description-Content-Type: text/markdown

# phzyx-auth

Secure-by-default authentication & authorization for Phzyx apps and AdminBoard.

**Public battery** for Forge (depends on `phzyx-forge` on PyPI).

```bash
uv add 'phzyx-forge[auth]'
# or
uv add 'phzyx-auth[fastapi]'
```

## Status (0.1.0)

**Phase A + B shipped** (memory backends; ORM-backed stores next):

| Area | What you get |
|------|----------------|
| Passwords | Argon2id, policy min length |
| Sessions | Opaque cookie sessions (hashed at rest), idle + absolute TTL |
| JWT | HS256 access + rotating refresh |
| API keys | `phx_` prefix, scopes, list/revoke |
| RBAC | Groups/Permissions models, Principal, `ensure_*` + Depends + decorators |
| Lockout | Failed-login counter + rate limits + audit log |
| Reset / change | Forgot/reset (anti-enum), change password, revoke sessions |
| Email verify | Token issue + verify (`email_verified` flag) |
| CSRF | Double-submit cookie + `X-CSRF-Token` for session mutations |
| WebSocket | `websocket_principal` (cookie / Bearer / API key, close 4401) |
| AdminBoard | `PhzyxAuthz` permission bridge |
| CLI | `phzyx-auth create-superuser` |

**Later:** OAuth IdPs (Phase D stub in `oauth.py`), MFA/passkeys (E), orgs (F), Redis rate limit, ORM session store.

## Quick start (FastAPI)

```python
from fastapi import Depends, FastAPI
from phzyx.auth import AuthConfig, AuthService, Principal
from phzyx.auth.adapters.fastapi import mount_auth, require_user

app = FastAPI()
svc = AuthService(
    config=AuthConfig(
        secret="use-PHZYX_AUTH_SECRET-32+bytes-in-prod!!",
        production=False,
        cookie_secure=False,
    )
)
mount_auth(app, svc)

@app.get("/private")
async def private(user: Principal = Depends(require_user)):
    return {"email": user.email}
```

```bash
# create staff superuser (in-memory unless you wire persistence)
phzyx-auth create-superuser --email admin@example.com --password 'longpassword1'
```

### SPA cookie flow

1. `POST /auth/login` → sets `phzyx_session` (HttpOnly) + `phzyx_csrf` + returns `csrf_token`
2. Mutating requests with the session cookie must send header `X-CSRF-Token: <csrf_token>`
3. Bearer JWT / `X-API-Key` skip CSRF

### In-body checks (no middleware)

```python
from phzyx.auth.guards import ensure_authenticated, ensure_permissions

p = svc.resolve_principal(session_token=cookie, authorization=authz, api_key=key)
user = ensure_authenticated(p)
ensure_permissions(user, ["posts:create"])
```

## Security defaults

- Argon2id hashing  
- Secrets ≥ 32 bytes (HS256 / pepper)  
- Opaque sessions + short JWT access  
- API keys hashed at rest  
- Lockout + IP/email rate limits  
- Password reset anti-enumeration  
- Extensible `AbstractUser`  

**Research (do not re-derive):** [`research/plan/20_phzyx_auth.md`](../../research/plan/20_phzyx_auth.md)  
**Tracker:** [`PHZYX_ROADMAP.html`](../../PHZYX_ROADMAP.html) track **AUTH**.
