Metadata-Version: 2.4
Name: sysnet-apikeys
Version: 1.0.0
Summary: SYSNET: správa API klíčů FastAPI služeb (hash, klient s více klíči, rotace s překryvem, scopes, admin API)
Author-email: SYSNET Development Team <info@sysnet.cz>
License-Expression: AGPL-3.0-or-later
Project-URL: Repository, https://github.com/SYSNET-CZ/apikeys
Project-URL: Changelog, https://github.com/SYSNET-CZ/apikeys/blob/main/CHANGELOG.md
Project-URL: Dokumentace, https://github.com/SYSNET-CZ/apikeys/tree/main/docs
Keywords: api-key,fastapi,authentication,rotation,sysnet
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Framework :: FastAPI
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers
Classifier: Topic :: Security
Classifier: Typing :: Typed
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: sysnet-pyutils>=1.7.8
Requires-Dist: PyYAML>=6.0.1
Requires-Dist: pydantic>=2.10.6
Provides-Extra: fastapi
Requires-Dist: fastapi>=0.115; extra == "fastapi"
Provides-Extra: sql
Requires-Dist: sqlalchemy[asyncio]>=2.0; extra == "sql"
Requires-Dist: asyncpg>=0.30; extra == "sql"
Provides-Extra: mongo
Requires-Dist: pymongo>=4.9; extra == "mongo"
Provides-Extra: cli
Requires-Dist: typer>=0.12; extra == "cli"
Provides-Extra: audit
Requires-Dist: sysnet-audit>=0.2.2; extra == "audit"
Provides-Extra: dev
Requires-Dist: fastapi>=0.115; extra == "dev"
Requires-Dist: sqlalchemy[asyncio]>=2.0; extra == "dev"
Requires-Dist: asyncpg>=0.30; extra == "dev"
Requires-Dist: pymongo>=4.9; extra == "dev"
Requires-Dist: typer>=0.12; extra == "dev"
Requires-Dist: sysnet-audit>=0.2.2; extra == "dev"
Requires-Dist: httpx>=0.27; extra == "dev"
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-asyncio>=0.26; extra == "dev"
Requires-Dist: ruff>=0.6; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"
Requires-Dist: types-PyYAML; extra == "dev"
Dynamic: license-file

# sysnet-apikeys

[![PyPI](https://img.shields.io/pypi/v/sysnet-apikeys)](https://pypi.org/project/sysnet-apikeys/)
[![Python](https://img.shields.io/pypi/pyversions/sysnet-apikeys)](https://pypi.org/project/sysnet-apikeys/)
[![Licence: AGPL-3.0-or-later](https://img.shields.io/badge/license-AGPL--3.0--or--later-blue)](LICENSE)
[![Stav](https://img.shields.io/pypi/status/sysnet-apikeys)](https://pypi.org/project/sysnet-apikeys/)
[![Typy: mypy strict](https://img.shields.io/badge/type%20checked-mypy%20strict-blue)](pyproject.toml)
[![Styl: ruff](https://img.shields.io/badge/code%20style-ruff-261230)](https://docs.astral.sh/ruff/)

Sdílená knihovna pro správu API klíčů FastAPI služeb SYSNET: klíč se ukládá jen jako SHA-256, klient může mít víc klíčů,
rotace probíhá s překryvem bez výpadku, klíče mají oprávnění (scopes) a platnost, správa je přes CLI nebo admin API.
Rozhodnutí a zdůvodnění: interní ADR 006 společnosti SYSNET.

**Stav: 1.0.0** (stabilní API). Jádro, úložiště (paměť, SQL, Mongo, starý YAML jen pro čtení), vazba na FastAPI, admin router, CLI a audit.
Pilotně nasazeno: `taxonomy` na vývojovém prostředí (Hermes VM, `APIKEYS_STORE=mongo`). **Produkční nasazení zatím neproběhlo.**

## Dokumentace

| Jste... | Dokument |
|---|---|
| Uživatel (volám službu s klíčem) | [docs/uzivatel.md](docs/uzivatel.md) |
| Programátor (zapojuji knihovnu) | [docs/programator.md](docs/programator.md) |
| Administrátor (spravuji klíče a provoz) | [docs/administrator.md](docs/administrator.md) |
| Manažer (rozhoduji) | [docs/manazer.md](docs/manazer.md) |

Přehled: [docs/README.md](docs/README.md). Změny: [CHANGELOG.md](CHANGELOG.md).

## Co řeší

- Klíč se ukládá jako SHA-256, otevřený klíč se vrací jednou při vydání.
- Klient má víc klíčů; **rotace** vydá nový klíč a starému zkrátí platnost na `nyní + grace`. Oba platí souběžně.
- Chybějící, neznámý, zrušený i propadlý klíč dává stejnou chybu (nic se nenapovídá útočníkovi).
- Identita se vrací hodnotou (`Principal`). Nesdílí se stav mezi požadavky, na rozdíl od starší funkce
  `Context.check_api_key` v `sysnet-pyutils`, která identitu ukládá do sdíleného stavu.
- Nové klíče mají tvar `snk_<public_id>_<tajemství>`; `public_id` je bezpečné uvádět v logu a v tiketu.

## Instalace

```bash
pip install "sysnet-apikeys[fastapi,mongo]"    # nebo [fastapi,sql]; CLI: přidejte cli, audit: přidejte audit
```

Python 3.11 a vyšší.

## Příklad (spouští se jako test)

```python
import asyncio
from datetime import timedelta
from typing import Annotated

import httpx
from fastapi import Depends, FastAPI

from sysnet_apikeys import ApiKeyService, InMemoryStore, Principal
from sysnet_apikeys.fastapi import ApiKeyAuth, install_error_handlers

service = ApiKeyService(InMemoryStore())
auth = ApiKeyAuth(service)
app = FastAPI()
install_error_handlers(app)


@app.get("/dokumenty")
async def dokumenty(principal: Annotated[Principal, Depends(auth)]) -> dict[str, str]:
    return {"volá": principal.client}


async def main() -> None:
    old = await service.issue("ippc-docs", scopes=["read"])
    new = await service.rotate("ippc-docs", grace=timedelta(days=1))
    async with httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://t") as c:
        for key in (old.key, new.key):
            print((await c.get("/dokumenty", headers={"X-API-KEY": key})).status_code)


asyncio.run(main())
```

Výstup: `200` a `200`. Starý i nový klíč platí do konce překryvu.

## Úložiště

| Úložiště | Extra | Poznámka |
|---|---|---|
| `MongoStore(db, collection=...)` | `mongo` | `pymongo.AsyncMongoClient`; `_id` = hash, unikátní `public_id` |
| `SqlStore(engine, table=...)` | `sql` | PostgreSQL (asyncpg); `create_schema()` vytvoří tabulku |
| `LegacyYamlStore` | (jádro) | jen pro čtení, pro migraci |
| `InMemoryStore` | (jádro) | testy a malé nástroje |

## Admin API a CLI

```python
app.include_router(make_admin_router(service, admin_key=os.environ.get("ADMIN_API_KEY")), prefix="/admin")
```

Proměnná se jmenuje `ADMIN_API_KEY` stejně jako v `notificator` a `geocode`. Bez `admin_key` vrací admin API 503.

**Vygenerování při nasazení** (jednou, hodnotu nikdy nevypíše, existující nepřepíše, nový soubor má práva 600):

```bash
sysnet-apikeys admin-key /opt/docker/<služba>/.env --ensure   # bez --ensure jen ověří (kód 1 = chybí)
```

CLI čte úložiště z proměnných `APIKEYS_STORE` (`mongo`|`sql`) a `APIKEYS_MONGO_URL`/`APIKEYS_MONGO_DB` nebo `APIKEYS_SQL_URL`.
Příkazy: `issue`, `rotate`, `revoke`, `list`, `unused`, `import-yaml`, `admin-key`. Otevřený klíč tiskne jen `issue` a `rotate`.
`import-yaml conf/x.yml` je dry-run, `--apply` zapíše hashe (klienti se nemění). Postupy najdete v [příručce administrátora](docs/administrator.md).

## Audit (sysnet-audit)

```python
from audit_lib import AuditLogger, MongoAuditBackend
from sysnet_apikeys.audit import audit_hook

service = ApiKeyService(
    store, audit=audit_hook(AuditLogger(backend=MongoAuditBackend(), service_name="moje-sluzba"))
)
```

Vydání, rotace a rušení klíče se zapíše jako `api_key.issued|rotated|revoked`. Klíč ani hash se nezapisují.
Selhání zápisu auditu operaci nezruší. Vyžaduje `pip install "sysnet-apikeys[audit]"` a nastavení `audit_lib`.

## Migrace ze starého YAML

`LegacyYamlStore.from_file("conf/xyz.yml")` čte stávající `api_keys: [{klíč: jméno}]`. Klíče dostanou oprávnění `*`
a `Principal.agenda` a `Principal.client` odpovídají dnešnímu `agenda` a `user_name`. Úložiště je jen pro čtení.

## Známá omezení

- Souběžné vydání klíče téhož klienta není atomické (může vzniknout víc aktivních klíčů). Podrobnosti v [příručce programátora](docs/programator.md).
- Hash bez soli je určen jen pro klíče s vysokou entropií, ne pro hesla.

## Vývoj

```bash
python -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/python -m pytest && .venv/bin/ruff check . && .venv/bin/mypy
```

Testy úložišť potřebují PostgreSQL a MongoDB (`APIKEYS_TEST_PG`, `APIKEYS_TEST_MONGO`); bez nich **padají**, nepřeskakují se.
Verzování: `pyproject.toml` a `sysnet_apikeys/__init__.py` musí mít stejnou verzi, tag `vX.Y.Z` spustí release.

## Licence

**GNU Affero General Public License v3.0 nebo novější** (`AGPL-3.0-or-later`), plný text v [LICENSE](LICENSE).
Stejně jsou licencované ostatní sdílené knihovny SYSNET, na kterých balíček stojí (`sysnet-pyutils`, `sysnet-audit`).
Pokud službu postavenou na této knihovně zpřístupníte uživatelům po síti, musíte jim nabídnout odpovídající zdrojový kód
včetně svých změn knihovny (§13). Interní provoz bez zpřístupnění třetím stranám tím dotčen není. Toto je obecná orientace, ne právní stanovisko.

Copyright (C) SYSNET s.r.o.
