Metadata-Version: 2.5
Name: tausi
Version: 0.1.0
Summary: Official Python SDK for Tausi: verify sessions and manage users from your backend, with Django and Flask integrations.
Project-URL: Homepage, https://tausi.app
Project-URL: Documentation, https://github.com/Nelacommunity/tausi-auth/tree/main/sdks/python#readme
Project-URL: Repository, https://github.com/Nelacommunity/tausi-auth
Project-URL: Issues, https://github.com/Nelacommunity/tausi-auth/issues
Project-URL: Changelog, https://github.com/Nelacommunity/tausi-auth/blob/main/sdks/python/CHANGELOG.md
Author: Tausi
License-Expression: MIT
License-File: LICENSE
Keywords: auth,authentication,django,flask,oauth,sessions,tausi
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: Django
Classifier: Framework :: Flask
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Internet :: WWW/HTTP :: Session
Classifier: Topic :: Security
Classifier: Typing :: Typed
Requires-Python: >=3.9
Provides-Extra: dev
Requires-Dist: django>=4.2; extra == 'dev'
Requires-Dist: djangorestframework>=3.15; extra == 'dev'
Requires-Dist: flask>=2.3; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Provides-Extra: django
Requires-Dist: django>=4.2; extra == 'django'
Provides-Extra: flask
Requires-Dist: flask>=2.3; extra == 'flask'
Description-Content-Type: text/markdown

# tausi (Python)

Official Python SDK for [Tausi](https://tausi.app), with Django, Django REST framework and Flask integrations. It needs Python 3.9 or later and has **no runtime dependencies**: it uses the standard library for HTTP.

| | Key | Use it for |
| --- | --- | --- |
| `tausi.TausiBackend` | secret key `sk_…` | Protecting your API (verifying session tokens) and managing your project's users. |
| `tausi.TausiClient` | publishable key `pk_…` | Server-rendered apps where Python signs the user in and keeps their token in the session, plus scripts and CLIs. |

```sh
pip install tausi            # core
pip install "tausi[django]"  # + Django
pip install "tausi[flask]"   # + Flask
```

## Protect your API

Your frontend (built with `@tausi-auth/js`, Flutter, etc.) sends the user's session token as `Authorization: Bearer <token>`. Your backend checks it with the secret key:

```python
import os
from tausi import TausiBackend

tausi = TausiBackend("https://tausi.app", os.environ["TAUSI_SECRET_KEY"])

auth = tausi.authenticate_request(request)   # any request with .headers, or the header value itself
if auth is None:
    ...                                       # 401
print(auth.user.id, auth.user.email, auth.session.expires_at)
```

`authenticate_request()` and `verify_session(token)` return `None` when a token is invalid, expired, revoked or belongs to another project. They raise `TausiError` only when the problem is on your side: a wrong key, a network failure or a 5xx response.

### Django

```python
# settings.py
TAUSI_URL = "https://tausi.app"
TAUSI_SECRET_KEY = os.environ["TAUSI_SECRET_KEY"]

# views.py
from django.http import JsonResponse
from tausi.django import tausi_required, tausi_optional

@tausi_required                   # 401 JSON without a valid token; works on async views too
def me(request):
    return JsonResponse(request.tausi.user.to_dict())

@tausi_optional                   # request.tausi is an AuthResult or None
def feed(request):
    ...
```

To set `request.tausi` on every request, add `"tausi.django.TausiAuthMiddleware"` to `MIDDLEWARE`. Only requests that carry a bearer token cost a call to Tausi. When Tausi can't be reached, the decorators and middleware answer `503` with code `AUTH_UNAVAILABLE`.

Each setting falls back to the environment variable of the same name (`TAUSI_URL`, `TAUSI_SECRET_KEY`, `TAUSI_PUBLISHABLE_KEY`). `TAUSI_TIMEOUT` sets the request timeout in seconds (default 10).

### Django REST framework

```python
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": ["tausi.django.rest_framework.TausiAuthentication"],
    "DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"],
}
```

In views, `request.user` is a `tausi.User` (with `is_authenticated = True`) and `request.auth` is the full `AuthResult`. Requests without a bearer token fall through to your other authentication classes.

### Flask

```python
from flask import Flask, g
from tausi.flask import Tausi

app = Flask(__name__)
app.config["TAUSI_URL"] = "https://tausi.app"
app.config["TAUSI_SECRET_KEY"] = os.environ["TAUSI_SECRET_KEY"]
tausi = Tausi(app)                # or tausi = Tausi(); tausi.init_app(app)

@app.get("/api/me")
@tausi.required                   # 401 JSON without a valid token
def me():
    return g.tausi.user.to_dict()

@app.get("/api/feed")
@tausi.optional                   # g.tausi is an AuthResult or None
def feed():
    ...
```

## Manage users

```python
page = tausi.users.list(q="asha", verified=True, page=1, page_size=50)   # Page[User]
for user in page.items:
    print(user.email, user.status, user.active_sessions, user.last_sign_in_at)

detail = tausi.users.get(user_id)        # {"user": User, "accounts": [...], "sessions": [...], "recentAudit": [...]}
tausi.users.update(user_id, name="Asha", email_verified=True)
tausi.users.revoke_sessions(user_id)     # sign out everywhere
tausi.users.delete(user_id)
tausi.sessions.list(page=1)
tausi.get_project()                      # startup check that the key works
```

From Django you get the configured backend with `tausi.django.get_backend()`; from Flask, `tausi.backend` inside an app context.

## Server-rendered sign-in

When your Python app renders the sign-in form itself, use `TausiClient` with the publishable key. The token stays in the server-side session and never reaches the browser.

### Django

```python
# settings.py: TAUSI_PUBLISHABLE_KEY = os.environ["TAUSI_PUBLISHABLE_KEY"], plus SessionMiddleware
from django.shortcuts import redirect, render
from tausi import TausiError
from tausi.django import get_client, tausi_login_required

def sign_in(request):
    if request.method == "POST":
        try:
            get_client(request).sign_in(request.POST["email"], request.POST["password"])
            return redirect(request.GET.get("next", "/"))
        except TausiError as e:
            return render(request, "sign_in.html", {"error": e.message, "code": e.code})
    return render(request, "sign_in.html")

def sign_out(request):
    get_client(request).sign_out()
    return redirect("/")

@tausi_login_required             # redirects to settings.LOGIN_URL with ?next=
def dashboard(request):
    return render(request, "dashboard.html", {"user": request.tausi.user})
```

### Flask

```python
@app.post("/sign-in")
def login():
    try:
        tausi.client().sign_in(request.form["email"], request.form["password"])
    except TausiError as e:
        return render_template("sign_in.html", error=e.message), 400
    return redirect(request.args.get("next", "/"))

@app.get("/dashboard")
@tausi.login_required(login_endpoint="login")
def dashboard():
    return render_template("dashboard.html", user=g.tausi.user)
```

### Social sign-in (PKCE)

```python
def start_google(request):
    start = get_client(request).start_oauth("google", redirect_to="https://example.com/auth/callback")
    request.session["tausi_verifier"] = start.code_verifier
    return redirect(start.url)

def callback(request):
    verifier = request.session.pop("tausi_verifier", "")
    try:
        get_client(request).handle_oauth_redirect(request.build_absolute_uri(), verifier)
    except TausiError as e:
        return render(request, "sign_in.html", {"error": e.message})
    return redirect("/")
```

`redirect_to` must be on one of the project's allowed origins.

### All client methods

`sign_up`, `sign_in`, `sign_out`, `get_session`, `send_verification_email`, `verify_email`, `forgot_password`, `reset_password`, `change_password`, `get_user`, `update_user`, `delete_account`, `list_sessions`, `revoke_session`, `revoke_other_sessions`, `get_providers`, `start_oauth`, `exchange_oauth_code`, `handle_oauth_redirect`.

Outside a web framework, `TausiClient` keeps the token in memory. Pass `token_store=` with any object that has `get()` and `set(token)` to keep it elsewhere; `MappingTokenStore(some_dict)` stores it in a dict-like session.

## Errors

Every failure is a `TausiError` with `status`, `code`, `message`, `details`, `request_id` and `retry_after` (seconds, for `RATE_LIMITED`). Network failures have `status == 0` and code `NETWORK_ERROR` or `TIMEOUT`. Branch on `code`, not on the message.

```python
try:
    client.sign_in(email, password)
except TausiError as e:
    if e.code == "EMAIL_NOT_VERIFIED":
        ...
```

## Custom HTTP

The default transport is `urllib` from the standard library. To use `requests`, `httpx` or a test double, pass `transport=` to either client: a callable `(method, url, headers, body, timeout) -> tausi.HttpResponse`.

## Development

```sh
python -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/pytest
```
