Metadata-Version: 2.5
Name: edge-authz
Version: 0.1.0
Summary: Edge authorization building blocks: client tokens, principals, decisions, a versioned and archived policy file, and a JSONL audit log. Framework-neutral.
Author-email: Stephan Eberle <stephaneberle9@gmail.com>
License-Expression: MIT
License-File: LICENSE
Requires-Python: >=3.11
Requires-Dist: tomlkit>=0.13
Description-Content-Type: text/markdown

# edge-authz <!-- omit in toc -->

Building blocks for an authorization enforcement point at the edge — a proxy
or gateway that decides, per request, whether a caller may do something, and
keeps a trace of every decision. Framework-neutral: no web framework, no
policy engine.

- [What it provides](#what-it-provides)
- [Installation](#installation)
- [Usage](#usage)
  - [Client tokens](#client-tokens)
  - [A versioned policy file](#a-versioned-policy-file)
  - [Decisions](#decisions)
  - [Audit log](#audit-log)
- [Contributing](#contributing)
- [License](#license)

## What it provides

| Piece | Purpose |
| --- | --- |
| `generate_token`, `hash_token`, `StaticTokenVerifier` | One random bearer token per client; only its SHA-256 is stored |
| `load_policy` | Reads a TOML policy file, requires a version number, and archives every version it loads |
| `create_client_token`, `revoke_client_token`, `edit_policy` | Change the policy file in place, keeping comments and raising its version |
| `Principal`, `Decision`, `PolicyBackend` | Who asks, what was decided and why, and the interface of whatever decides |
| `JsonlAuditSink`, `pseudonymize` | An append-only audit log flushed to disk per entry, and keyed pseudonyms for identifiers |

What a policy allows is the application's business; this package only
supplies the plumbing around it.

## Installation

The package is not published to PyPI. Install it from its Git repository:

```bash
uv add "edge-authz @ git+https://github.com/stephaneberle9/python-edge-authz.git"
```

Requires Python 3.11+.

## Usage

### Client tokens

```python
from edge_authz import StaticTokenVerifier

verifier = StaticTokenVerifier(
    {"agent-a": "<sha256 of agent-a's token>"},
    {"agent-a": {"may_write": True}},
)
principal = verifier.verify_authorization(request.headers.get("authorization"))
if principal is None:
    ...  # 401
```

Tokens carry 256 random bits, so a plain SHA-256 is enough to store them; all
stored hashes are compared in constant time.

### A versioned policy file

```toml
[proxy]
version = 3

[clients.agent-a]
token_sha256 = "…"
may_write = true
```

```python
from edge_authz import create_client_token, load_policy

policy = load_policy("policy.toml", "policy-archive", version_table="proxy")
token = create_client_token("policy.toml", "agent-b", version_table="proxy", prefix="dcp_")
print(token)  # shown once; only its hash is in the file
```

`load_policy` copies each version it loads to `<archive>/<version>-<sha256>.toml`
and refuses a version that was archived before with different content, so a
decision logged with a version and hash can always be traced back to the exact
policy, even when the file itself is not under version control. Editing the
file by hand means raising `version`; the token functions do that themselves.

### Decisions

```python
from edge_authz import Decision, Principal

class MyPolicy:
    async def decide(self, principal: Principal, action: str, attributes) -> Decision:
        if action in {"read"}:
            return Decision.allow("reads")
        return Decision.deny("action_not_enabled", "default")
```

### Audit log

```python
from edge_authz import JsonlAuditSink, pseudonymize

audit = JsonlAuditSink("audit/audit.jsonl")
audit.write({"client": principal.id, "decision": "allow", "subject": pseudonymize(key, subject_id)})
```

`write` raises when the entry could not be stored; refuse the request then.
Pseudonymize identifiers that are personal data with a secret key: plain
hashes of short identifiers are easy to reverse by trying values.

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for development setup and guidelines.

## License

MIT — see [LICENSE](LICENSE).
