Metadata-Version: 2.4
Name: tenon-auth
Version: 1.0.0
Summary: Tenon-Auth SDK for Python: multi-factor authentication for your application's users
License-Expression: Apache-2.0
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Classifier: Topic :: Security
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# Tenon-Auth SDK for Python

Multi-factor authentication for your application's users, from Python 3.9+ (Django, Flask, FastAPI, scripts). It uses the standard library only. The full guide is on the developer site (https://docs.glow-kin.com); the contract all SDKs follow is [../README.md](../README.md).

## Install

Until it is published on PyPI, install the wheel from the developer site's downloads (check its SHA-256):

```bash
pip install tenon_auth-1.0.0-py3-none-any.whl
```

## Use

```python
import os
from tenon_auth import SignInContext, TenonAuthClient, TenonAuthUnavailableError

tenon_auth = TenonAuthClient(
    os.environ["TENON_AUTH_URL"],        # https://api.glow-kin.com
    os.environ["TENON_AUTH_API_KEY"],    # tnk_..., from the admin console
)

# After the password was right:
try:
    step = tenon_auth.begin_second_step(
        str(account.id),
        SignInContext(ip_address=client_ip, user_agent=request.headers.get("User-Agent")),
        trusted_device_token=request.cookies.get("tenon_device"),
        language="ja",
    )
except TenonAuthUnavailableError:
    ...  # fail closed: refuse the sign-in, except your logged emergency accounts

session["second_step"] = step.to_dict()          # server-side session only
# step.kind: CODE_ENTRY (step.method TOTP or EMAIL), PUSH_WAIT (show step.number, poll_approval every 2 s),
#            TRUSTED, NO_METHOD (your policy), RECOVERY_ONLY (no email could be sent)

# The code page's form:
result = tenon_auth.complete_with_code(SecondStep.from_dict(session["second_step"]), form["code"])
if result.approved:
    ...  # signed in
```

Setting up a method: `enroll_totp` (show `qr_code`, then `activate_factor` with the first code), `enroll_push`, or `enroll_email`; then `create_recovery_codes` once, and show the codes.

**Errors** are `TenonAuthError` subclasses:
- `TenonAuthUnavailableError` (fail closed) and `ApiKeyRejectedError` (configuration);
- `InvalidCodeError`, `FactorLockedError`, `TooManyRequestsError`;
- `EmailRefusedError`, `EmailUnavailableError`;
- `NotFoundError`, `ConflictError`, `InvalidRequestError`, `ForbiddenError`.

A wrong code at sign-in is not an error: `verified` is `False`.

**Networking:**
- **Proxies:** the usual environment variables (`HTTPS_PROXY`).
- **A company CA:** pass `ssl_context=`.
- **Redirects:** never followed, so the key can't be sent elsewhere.

## Test

```bash
python -m unittest -v tests.test_client          # against a local fake server
python ../conformance/run.py python              # against the real backend (bootTestRun)
```
