Metadata-Version: 2.5
Name: keycloak-sso-gateway
Version: 0.1.0
Summary: Ready-to-run OIDC / Keycloak SSO callback server for Telegram bots and web apps
Project-URL: Homepage, https://github.com/ggb0yyyyyyy/keycloak-sso-gateway
Project-URL: Repository, https://github.com/ggb0yyyyyyy/keycloak-sso-gateway
Project-URL: Issues, https://github.com/ggb0yyyyyyy/keycloak-sso-gateway/issues
Project-URL: Documentation, https://github.com/ggb0yyyyyyy/keycloak-sso-gateway#readme
Author-email: Mikhail Logunov <milog7890@gmail.com>
License: MIT
License-File: LICENSE
Keywords: gateway,keycloak,oauth2,oidc,server,sso,telegram
Classifier: Development Status :: 4 - Beta
Classifier: Framework :: AsyncIO
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications :: Chat
Classifier: Topic :: Internet :: WWW/HTTP :: Session
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: aiohttp>=3.9
Requires-Dist: pydantic>=2.4
Requires-Dist: pyjwt[crypto]>=2.8
Provides-Extra: dev
Requires-Dist: pytest-aiohttp>=1.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Provides-Extra: redis
Requires-Dist: redis>=5.0; extra == 'redis'
Description-Content-Type: text/markdown

# keycloak-sso-gateway

[![PyPI](https://img.shields.io/pypi/v/keycloak-sso-gateway.svg)](https://pypi.org/project/keycloak-sso-gateway/)
[![Python](https://img.shields.io/pypi/pyversions/keycloak-sso-gateway.svg)](https://pypi.org/project/keycloak-sso-gateway/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)

Ready-to-run OIDC / **Keycloak** callback **server** for a fleet of Telegram
bots and domain web apps. One public HTTPS origin; configure targets via env
and deploy with Docker Compose (or `python -m keycloak_sso_gateway`).

**Companion bot adapter:**
[`aiogram-keycloak-sso`](https://pypi.org/project/aiogram-keycloak-sso/)
([GitHub](https://github.com/ggb0yyyyyyy/sso-aiogram-midleware)) —
bots never expose HTTP; this gateway owns `/sso/callback`. Wire DTOs
(`SSOSession`, login events, Redis prefixes) are duplicated in both packages
and must stay in sync (`schema_version`).

![SSO login flow](https://raw.githubusercontent.com/ggb0yyyyyyy/keycloak-sso-gateway/master/docs/sso-flow.svg)

## Install

```bash
pip install "keycloak-sso-gateway[redis]"
# or without Redis extras if you only need the library pieces:
pip install keycloak-sso-gateway
```

Import package name (underscore): `keycloak_sso_gateway`.

CLI entry points:

```bash
keycloak-sso-gateway
# same as:
python -m keycloak_sso_gateway
```

## Local run

```bash
cp .env.example .env   # fill SSO_* / GATEWAY_* / REDIS_URL
pip install -e ".[redis]"
python -m keycloak_sso_gateway
```

`main()` auto-loads `.env` from the current working directory (or the path in
`SSO_ENV_FILE`). Existing process environment variables are never overwritten.
Missing file → no-op.

## Docker Compose

Host port **8091** → container **8080**. Shared network name: `sso-fleet`.

```bash
cp .env.example .env   # fill SSO_* / GATEWAY_*
docker network create sso-fleet   # once
docker compose up -d --build
# local: http://localhost:8091/sso/callback
```

Compose still uses `env_file: .env` and overrides `REDIS_URL` / `PORT` for the
container. `PUBLIC_CALLBACK_URL` must be the public HTTPS URL registered in
Keycloak (edge TLS in front of `:8091`), not the container-local address.

## Public URLs

| Method | Path | Purpose |
|--------|------|---------|
| `GET` | `/sso/login` | Start **web** login (`app_id`, `return_uri`, optional `next`) → 302 to Keycloak |
| `GET` | `/sso/callback` | Keycloak redirect (`code` + `state`) — single fleet `redirect_uri` |
| `POST` | `/sso/handoff` | Web app redeems one-shot handoff: `{"handoff":"..."}` → tokens JSON |

Bots never call `/sso/login`; they build the authorize URL themselves (via
`aiogram-keycloak-sso`) and only need this gateway as the shared callback +
Redis session/event bus.

## Modes

### Bot (shared Redis)

1. Bot builds authorize URL with `state=bot:<bot_id>:<hmac>` (legacy
   `<bot_id>:<hmac>` still accepted).
2. Keycloak redirects to `/sso/callback`.
3. Gateway exchanges the code, writes the session to Redis, publishes a login
   event, redirects to `t.me/...` or shows success HTML.

### Web (one-shot handoff)

1. Browser (or web backend redirect) hits  
   `GET /sso/login?app_id=portal&return_uri=https://app.example.com/sso/complete&next=/dashboard`  
   (`return_uri` must be allowlisted in `GATEWAY_WEB_APPS`).
2. Gateway 302 → Keycloak; Keycloak returns to the **same** `/sso/callback`.
3. Gateway stores tokens under a handoff id (TTL ~120s) and `302` to
   `return_uri?handoff=...`.
4. Web backend `POST /sso/handoff` once, gets tokens + claims, sets its own
   cookie/session.

Web apps do **not** poll Redis bot sessions.

## Environment

| Variable | Role |
|----------|------|
| `PORT` | Listen port (default `8080`) |
| `REDIS_URL` | Redis for sessions, events, handoffs |
| `PUBLIC_CALLBACK_URL` | Fleet-wide OIDC `redirect_uri` |
| `SSO_ISSUER` / `SSO_CLIENT_ID` / `SSO_CLIENT_SECRET` | One confidential Keycloak client |
| `SSO_STATE_SECRET` | HMAC base secret (namespaced per bot/app id) |
| `GATEWAY_BOT_IDS` | Comma-separated bot ids |
| `GATEWAY_WEB_APPS` | Web app allowlist (see below) |
| `POST_LOGIN_REDIRECT_TEMPLATE` | Optional `https://t.me/{bot_id}_bot`; unset → HTML success page |
| `SSO_ENV_FILE` | Optional dotenv path (default `.env` in cwd) |

### `GATEWAY_WEB_APPS`

```text
app_id=https://app.example.com/sso/complete;crm=https://crm.example.com/done,https://crm.example.com/dev
```

- `;` separates apps  
- `,` separates allowlisted `return_uri` values for one app  
- empty / unset → bots only  

## Wire contract

Redis key prefixes and JSON shapes (`SSOSession`, login events, handoff
payload, `schema_version`) are the shared bus contract with bot processes
(`aiogram-keycloak-sso`). Keep them in sync with consumers.

## Development

```bash
pip install -e ".[dev,redis]"
pytest
```

Optional smoke helpers live under `integration/` when present.
