Metadata-Version: 2.4
Name: intigin
Version: 1.2.1
Summary: Intigin dynamic-backend adapter — thin in-app WSGI middleware for view-source protection.
Author: Intigin
Project-URL: Homepage, https://intigin.com
Project-URL: Documentation, https://intigin.com/docs
Keywords: intigin,wsgi,middleware,view-source,protection,flask,django
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware
Classifier: Topic :: Security
Requires-Python: >=3.7
Description-Content-Type: text/markdown

# intigin

Thin in-app **WSGI adapter** for [Intigin](https://intigin.com) dynamic-backend protection. It wraps
your app so the same Intigin bundle that protects static sites also protects your server-rendered pages —
view-source shows only a loader shell, and the real HTML is reconstructed client-side.

The adapter does almost nothing: the browser bundle does the heavy lifting. On a page navigation it
returns a **clean loader shell** (no payload, no cookie); when the bundle asks for the page (a same-URL
fetch carrying an `X-Intigin-Payload` header) it encodes the finished HTML into the frozen wire format
and returns it. The bundle at `https://{host}/{token}.js` decodes and reconstructs the page, then
reveals, polls, and hot-swaps live like a static Intigin page — no backend round-trip on updates.

## Install

```bash
pip install intigin
```

## Use

Wrap your WSGI callable with a scoped Gateway API key (dashboard → **Settings → Gateway API**). The
adapter resolves your public bundle token *and* your private backend secret from the key once at boot,
so neither is hardcoded:

```python
from intigin import Intigin

app.wsgi_app = Intigin(app.wsgi_app, api_key="iga_...", host="intigin.com")   # recommended
```

Flask uses `app.wsgi_app`; Django wraps `application` in `wsgi.py`. A raw 32-hex `token="..."` plus your
dashboard's `secret="..."` is also accepted when you'd rather not hold an `iga_` key.

## Behavior

- **Authenticated marker-fetch.** The bundle re-fetches the page with an `X-Intigin-Payload` header;
  the adapter answers only when that request carries a valid `X-Intigin-Backend-Secret` (a genuine
  third-party backend) or the adapter's own `ig_nav` cookie (minted on the preceding navigation,
  proving a real recent page load — not a detection outcome). Neither check touches a database: the
  cookie is a short-TTL HMAC signed with your backend secret and verified by recomputation, so this
  adds no per-request server load. Anything else gets a self-contained restricted page, not your HTML.
- **`ig_nav` cookie, not payload-bearing.** `HttpOnly; Secure; SameSite=Lax; Max-Age=60`, set on the
  loader-shell response only. It authenticates the follow-up marker fetch; the page HTML itself never
  rides a cookie or appears in view-source.
- **Query-aware wire cache.** Cached payloads are keyed by path, sorted query string, **and** which
  credential authenticated them, so `/?s=…` search never reuses the homepage wire for `/`, and a
  secret-authenticated wire (keyed differently) is never handed to the cookie/nonce path or vice versa.
- **Non-HTML passes through** untouched; only top-level HTML navigations are wrapped.
- **Fail-closed.** A bad/missing key, a failed auth check, or any wrap error returns a self-contained
  restricted page — never your raw HTML.
- **Back-compat, logged.** An older `token="..."` setup with no `secret=`/`api_key=` still answers
  marker-fetches (unauthenticated, as before this version) but logs a warning on every use — pass
  `api_key=` or `secret=` to close it.
- **Kill-switch.** `INTIGIN_ADAPTER_MODE=wrap|passthrough|off` (env). `passthrough`/`off` removes the
  adapter from the path.

## Conformance

```bash
python -m intigin
```

Verifies the frozen wire format — `base64("IGP1" + FLAGS + BODY)`, `BODY = xor(deflate(utf8(html)),
key)`, `key = sha256(token)` (cookie/nonce path) or `sha256(token + "|" + secret)` (backend-secret
path) — plus nav-nonce mint/verify, against the browser bundle's own decoder.

> **Concealment, not encryption.** The cookie/nonce-path scramble key is derived from the public bundle
> token, so on its own it defeats view-source and naive scrapers, not cryptanalysis — but reaching it
> now requires a real recent navigation (or your private backend secret), not just reading a token off
> a page's source. Object-level authorization stays in your own routes.
