Metadata-Version: 2.5
Name: lexigram-auth
Version: 0.1.3007
Summary: Authentication and authorization for Lexigram Framework - JWT, OAuth2, SAML, LDAP, RBAC, and multi-tenancy
Project-URL: Homepage, https://lexigram.dev
Project-URL: Repository, https://github.com/dbtinoy-/lexigram
Project-URL: Documentation, https://docs.lexigram.dev
Project-URL: Issues, https://github.com/dbtinoy-/lexigram/issues
Project-URL: Changelog, https://github.com/dbtinoy-/lexigram/blob/main/CHANGELOG.md
Author-email: Lexigram Framework Team <team@lexigram.dev>
Maintainer-email: Lexigram Framework Team <team@lexigram.dev>
License: MIT
License-File: LICENSE
Keywords: auth,authentication,authorization,framework,jwt,ldap,multi-tenancy,oauth2,rbac,saml,security
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
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: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: argon2-cffi>=21.1.0
Requires-Dist: cryptography>=46.0.5
Requires-Dist: jinja2>=3.1.0
Requires-Dist: lexigram-contracts>=0.1.0
Requires-Dist: lexigram>=0.1.1
Requires-Dist: orjson>=3.9.0
Requires-Dist: passlib[bcrypt]>=1.7.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: pyjwt>=2.0.0
Requires-Dist: starlette>=0.28.0
Requires-Dist: typer>=0.9.0
Requires-Dist: typing-extensions>=4.0.0
Provides-Extra: admin
Requires-Dist: lexigram-admin>=0.1.1; extra == 'admin'
Provides-Extra: all
Requires-Dist: authlib>=1.0.0; extra == 'all'
Requires-Dist: ldap3>=2.9.0; extra == 'all'
Requires-Dist: lexigram-testing>=0.1.1; extra == 'all'
Requires-Dist: pysaml2>=7.0.0; extra == 'all'
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'all'
Requires-Dist: pytest-cov>=4.0.0; extra == 'all'
Requires-Dist: pytest-mock>=3.10.0; extra == 'all'
Requires-Dist: pytest>=8.0.0; extra == 'all'
Requires-Dist: xmlsec>=1.3.0; extra == 'all'
Provides-Extra: dev
Requires-Dist: black>=23.0.0; extra == 'dev'
Requires-Dist: mypy>=1.0.0; extra == 'dev'
Requires-Dist: ruff>=0.1.0; extra == 'dev'
Provides-Extra: ldap
Requires-Dist: ldap3>=2.9.0; extra == 'ldap'
Provides-Extra: oauth2
Requires-Dist: authlib>=1.0.0; extra == 'oauth2'
Provides-Extra: saml
Requires-Dist: pysaml2>=7.0.0; extra == 'saml'
Requires-Dist: xmlsec>=1.3.0; extra == 'saml'
Provides-Extra: test
Requires-Dist: lexigram-testing>=0.1.1; extra == 'test'
Requires-Dist: pytest-asyncio>=0.21.0; extra == 'test'
Requires-Dist: pytest-cov>=4.0.0; extra == 'test'
Requires-Dist: pytest-mock>=3.10.0; extra == 'test'
Requires-Dist: pytest>=8.0.0; extra == 'test'
Description-Content-Type: text/markdown

# lexigram-auth

Authentication and authorization for the Lexigram Framework — JWT, OAuth2, SAML, RBAC, and multi-tenancy.

---

## Overview

Complete authentication and authorization stack for Lexigram — JWT, OAuth2, RBAC,
SAML, passkeys, and MFA. Provides a production-ready auth layer with multiple
authentication strategies, policy-based access control, session management, and
seamless integration with `lexigram-web` middleware.

Use `AuthModule.configure()` to register the auth bundle and protect routes with
`@require_auth`, `@require_roles`, and `@require_permissions` decorators.


> Full documentation: [docs.lexigram.dev](https://docs.lexigram.dev)
## Install

```bash
uv add lexigram-auth
# Optional extras
uv add "lexigram-auth[oauth2,saml]"
```

## Quick Start

```python
from lexigram import Application
from lexigram.di.module import Module, module
from lexigram.auth import AuthModule, AuthConfig, JWTConfig

@module(imports=[
    AuthModule.configure(
        config=AuthConfig(
            secret_key="your-secret-key",
            token=JWTConfig(secret_key="your-jwt-secret"),
        )
    )
])
class AppModule(Module):
    pass


async def main() -> None:
    async with Application.boot(modules=[AppModule]) as app:
        # app is running — resolve services from app.container
        ...


if __name__ == "__main__":
    import asyncio

    asyncio.run(main())
```

## Configuration

> **Note:** `AuthConfig` requires both `secret_key` and `token.secret_key` — pass an explicit config via `AuthModule.configure()`.

### Option 1 — YAML file

```yaml
# application.yaml
auth:
  secret_key: "your-secret-key"
  token:
    secret_key: "your-jwt-secret"
    algorithm: "HS256"
    access_token_expire: "30m"
  rbac:
    enabled: true
    default_role: "viewer"
```

### Option 2 — Profiles + Environment Variables *(recommended)*

```bash
export LEX_AUTH__SECRET_KEY=your-secret-key
export LEX_AUTH__TOKEN__SECRET_KEY=your-jwt-secret
export LEX_AUTH__TOKEN__ALGORITHM=HS256
export LEX_AUTH__RBAC__DEFAULT_ROLE=viewer
```

### Option 3 — Python

```python
from lexigram.auth import AuthModule, AuthConfig, JWTConfig
from lexigram.contracts.core import Duration

config = AuthConfig(
    secret_key="your-secret-key",
    token=JWTConfig(
        secret_key="your-jwt-secret",
        algorithm="HS256",
        access_token_expire=Duration.minutes(30),
    ),
)
AuthModule.configure(config)
```

### Config reference

| Field | Default | Env var | Description |
|-------|---------|---------|-------------|
| `secret_key` | — | `LEX_AUTH__SECRET_KEY` | Top-level signing secret (**required**) |
| `token.secret_key` | — | `LEX_AUTH__TOKEN__SECRET_KEY` | JWT signing secret (**required**) |
| `token.algorithm` | `HS256` | `LEX_AUTH__TOKEN__ALGORITHM` | JWT algorithm: HS256, RS256, ES256 |
| `token.access_token_expire` | `30m` | `LEX_AUTH__TOKEN__ACCESS_TOKEN_EXPIRE` | Access token lifetime (duration string, e.g. `30m`, `1h30m`) |
| `rbac.enabled` | `True` | `LEX_AUTH__RBAC__ENABLED` | Enable RBAC |
| `rbac.default_role` | `viewer` | `LEX_AUTH__RBAC__DEFAULT_ROLE` | Default role for new users |

## Module Factory Methods

| Method | Description |
|--------|-------------|
| `AuthModule.configure(...)` | Configure with explicit AuthConfig |
| `AuthModule.stub()` | Minimal config for testing |

## Key Features

- **JWT authentication** — HS256/RS256, key rotation, token blacklisting
- **OAuth2 / OIDC** — authlib-backed: Google, GitHub, custom providers
- **SAML 2.0** — Enterprise SSO via pysaml2
- **Passkeys (WebAuthn)** — FIDO2 device-based authentication
- **MFA (TOTP)** — Time-based one-time passwords
- **RBAC** — Role/permission inheritance with policy expressions
- **Session management** — Device-aware sessions with concurrency limits
- **Token binding** — IP address binding to prevent token theft

## Testing

```python
async with Application.boot(modules=[AuthModule.stub()]) as app:
    # your test code
    ...
```

## Key Source Files

| File | What it contains |
|------|-----------------|
| `src/lexigram/auth/module.py` | AuthModule definition |
| `src/lexigram/auth/config.py` | AuthConfig, JWTConfig (+ `allow_unverified_dev`), RBACConfig |
| `src/lexigram/auth/di/bundle_provider.py` | AuthBundleProvider wiring |
| `src/lexigram/auth/di/sub_providers/token_provider.py` | TokenProvider (boots policy) |
| `src/lexigram/auth/authn/jwt.py` | JWTTokenManager implementation |
| `src/lexigram/auth/authn/_jwt_lifecycle.py` | verify_token (enforces policy) |
| `src/lexigram/auth/authz/service.py` | AuthorizationService |

## JWT verification policy

`lexigram-auth` enforces **verified-only** JWT decoding by default.

| Environment | Secret present | `allow_unverified_dev` | Behaviour |
|-------------|---------------|------------------------|-----------|
| `PRODUCTION` / `STAGING` | yes | any | Verified-only. Boot succeeds. |
| `PRODUCTION` / `STAGING` | **no** | any | **Raises `ConfigurationError` at boot.** Flag ignored. |
| `DEVELOPMENT` | yes | any | Verified-only. Boot succeeds. |
| `DEVELOPMENT` | **no** | `False` (default) | **Raises `ConfigurationError` at boot.** |
| `DEVELOPMENT` | **no** | `True` | Boots. Single warning logged. Tokens decoded **without** signature verification. |

### Enable the dev opt-in

Via environment variable:
```bash
export LEX_AUTH__TOKEN__ALLOW_UNVERIFIED_DEV=true
```

Via Python config:
```python
from lexigram.auth.config import AuthConfig, JWTConfig

config = AuthConfig(
    secret_key="any-placeholder",
    token=JWTConfig(
        secret_key="any-placeholder",
        allow_unverified_dev=True,
    ),
)
```

The `allow_unverified_dev` flag is **silently ignored** in `PRODUCTION` and `STAGING`; the
service always rejects the flag in those environments and raises if no real secret is
configured. This prevents the Piccolina-style mistake of silently trusting unverified tokens
in production when a secret env-var is missing.
