Metadata-Version: 2.4
Name: fleetmesh-auth
Version: 0.1.0
Summary: Pluggable white-labeled FleetMesh authentication: DID, NIP-46 bunker, NIP-07, browser-signed keys and service providers
License-Expression: CC0-1.0
Project-URL: Source, https://gitlab.pocketlabs.dev/fleetmesh/fleetmesh-auth
Keywords: fleetmesh,nostr,nip-46,nip-07,did,authentication,sso
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Internet :: WWW/HTTP :: Session
Classifier: Topic :: Security
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: fleetmesh-core>=0.2.0
Dynamic: license-file

# FleetMesh Auth (`fleetmesh/fleetmesh-auth`)

Standardized, white-labeled, and pluggable authentication & authorization suite for FleetMesh applications (**Fleetmesh Stream Studio**, **MeshTube Admin Deck**, **Fleetmesh Meshnode Ops**, and standalone mesh services).

## Key Features
1. **Cryptographic & Sovereign Identity Login (`sovereign_key`)**:
   - **Nostr Bunker (`bunker://<pubkey>?relay=wss://...`)**: NIP-46 remote signer URI parsing and signed challenge verification. A bunker login must carry a signature over a server challenge; the bundled widget does not yet run the NIP-46 handshake that gets one, so its bunker tab cannot complete a login.
   - **NIP-07 Browser Signer (`window.nostr.signEvent`)**: One-click cryptographic challenge signing via browser extension.
   - **Private Key / DID (`nsec1...`, 64-char hex, `did:nostr`, `did:key`)**: the panel signs the challenge in the page (BIP-340 over WebCrypto, `BROWSER_KEY_SIGNER_JS` in `ui_widget.py`) and sends only the public key and signature. The server refuses a raw key outright (`private_key_not_accepted`). It only signs the `<scheme>:<challenge>:<origin>` login message it hashes itself, never a digest the server hands it.
   - **Portable Cross-Site Identity Pass (`fmpass_...` / `mtpass_...`)**: Self-contained signed NIP-98 (`Kind 27235`) SSO tickets. A ticket is the identity of the key that signs it, so only the user's own key can mint one, and the panel mints it in the browser: from a pasted key at sign-in, before the key is cleared, or from the signer extension when "Copy Portable Pass" is clicked. The server mints none, and deletes the ticket-signing key earlier versions kept. A ticket is good for one sign-in within the hour; the copy button never hands out the session token.
2. **Pluggable Service & User-Store Providers (`AuthProviderPlugin`)**:
   - **`MeshTubeAuthProvider` (`meshtube`)**: Authenticates against a local or remote MeshTube instance's `users`, `admin_users`, and `api_keys` store.
   - **`LocalOperatorProvider` (`local_operator`)**: Standalone node operator authentication via `MESHNODE_OPERATOR_TOKEN`, `meshctl` control credentials, or authorized operator pubkeys—no MeshTube dependency required.
3. **White-Labeled FleetMesh Auth Panel (`ui_widget.py`)**:
   - Supports **Full-Screen Auth Gate** mode (`admin.<domain>`, `ops.<domain>`) and **Slide-Over / Modal** mode (`studio.<domain>`).
   - Mountable `/api/fauth/*` HTTP routes (`GET /api/fauth/config`, `GET /api/fauth/session`, `POST /api/fauth/challenge`, `POST /api/fauth/login`, `POST /api/fauth/logout`, `GET /api/fauth/widget.js`) or runnable as a standalone SSO service (`python3 -m fleetmesh_auth`).

## Configuration

Apps build `FleetmeshAuthEngine` themselves; these environment variables set
who holds which role without changing their code. Lists are separated by
commas or whitespace. Keys are 64-hex x-only pubkeys, `npub1...` or
`did:nostr:...`; an entry that does not parse is logged and skipped.

| Variable | Effect |
|---|---|
| `FLEETMESH_AUTH_OPERATOR_PUBKEYS` | Keys that sign in as node operators (with `admin`). |
| `FLEETMESH_AUTH_ADMIN_PUBKEYS` | Keys that sign in as admins. `admin` and `node_operator` imply each other in `principal.ROLE_IMPLICATIONS`, so today this grants the same roles as the operator list. |
| `FLEETMESH_AUTH_TRUSTED_INSTANCES` | Remote MeshTube base URLs a login may name in `instance_url`. The provider's own `instance_url` is always trusted; users verified remotely sign in as creators. |
| `MESHNODE_OPERATOR_TOKEN` (or `FLEETMESH_OPERATOR_TOKEN`, `MESHTUBE_ADMIN_PASS`) | The local operator provider's password. Unset, operator-token login is refused; there is no default. |
| `MESHNODE_OPERATOR_USER` (or `MESHTUBE_ADMIN_USER`) | The operator's display name (default `admin`). |

Without any of these, a key login is a creator. It is an operator only when the
key is the co-hosted studio's own operator or node key, or the studio's user
record has an admin or operator role. `allow_any_sovereign_key=True` (code only)
makes every key an operator, for single-operator setups. A list passed to a
provider's constructor replaces the environment's.

## Tests

```
PYTHONPATH=.:../core:../schema python3 tests/test_fleetmesh_auth.py
PYTHONPATH=.:../core:../schema python3 tests/test_browser_signer.py   # needs node 22+; skipped without it
```

`test_browser_signer.py` runs the panel's in-page signer under Node against
`fleetmesh_core`: identical public keys and signatures, nsec decoding, and the
server accepting what the page signs, and passes minted in the page that sign in as their owner once.
