Metadata-Version: 2.4
Name: sanoptis-auth
Version: 0.4.0
Summary: Microsoft SSO + admin-broker credential client for Sanoptis Claude Code plugins.
Project-URL: Homepage, https://github.com/Sanoptis-BI/sanoptis-auth
Project-URL: Repository, https://github.com/Sanoptis-BI/sanoptis-auth
Project-URL: Issues, https://github.com/Sanoptis-BI/sanoptis-auth/issues
Author-email: Sanoptis-BI <data-ai@sanoptis.com>
License: Copyright (c) 2026 Sanoptis AG
        
        # PolyForm Internal Use License 1.0.0
        
        <https://polyformproject.org/licenses/internal-use/1.0.0>
        
        ## Acceptance
        
        In order to get any license under these terms, you must agree
        to them as both strict obligations and conditions to all
        your licenses.
        
        ## Copyright License
        
        The licensor grants you a copyright license for the
        software to do everything you might do with the software
        that would otherwise infringe the licensor's copyright in
        it for any permitted purpose.  However, you may only make
        changes or new works based on the software according to
        [Changes and New Works License](#changes-and-new-works-license),
        and you may not distribute the software.
        
        ## Internal Business Use
        
        Use of the software for the internal business operations
        of you and your company is use for a permitted purpose.
        
        ## Changes and New Works License
        
        The licensor grants you an additional copyright license
        to make changes and new works based on the software for
        any permitted purpose.
        
        ## Patent License
        
        The licensor grants you a patent license for the software
        that covers patent claims the licensor can license, or
        becomes able to license, that you would infringe by using
        the software.
        
        ## Fair Use
        
        You may have "fair use" rights for the software under the
        law. These terms do not limit them.
        
        ## No Other Rights
        
        These terms do not allow you to sublicense or transfer any
        of your licenses to anyone else, or prevent the licensor
        from granting licenses to anyone else. These terms do not
        imply any other licenses.
        
        ## Patent Defense
        
        If you make any written claim that the software infringes
        or contributes to infringement of any patent, your patent
        license for the software granted under these terms ends
        immediately. If your company makes such a claim, your
        patent license ends immediately for work on behalf of your
        company.
        
        ## Violations
        
        The first time you are notified in writing that you have
        violated any of these terms, or done anything with the
        software not covered by your licenses, your licenses can
        nonetheless continue if you come into full compliance
        with these terms, and take practical steps to correct past
        violations, within 32 days of receiving notice.  Otherwise,
        all your licenses end immediately.
        
        ## No Liability
        
        ***As far as the law allows, the software comes as is,
        without any warranty or condition, and the licensor
        will not be liable to you for any damages arising out of
        these terms or the use or nature of the software, under
        any kind of legal claim.***
        
        ## Definitions
        
        The **licensor** is the individual or entity offering these
        terms, and the **software** is the software the licensor makes
        available under these terms.
        
        **You** refers to the individual or entity agreeing to these
        terms.
        
        **Your company** is any legal entity, sole proprietorship,
        or other kind of organization that you work for, plus all
        organizations that have control over, are under the control
        of, or are under common control with that organization.
        **Control** means ownership of substantially all the assets of
        an entity, or the power to direct its management and policies
        by vote, contract, or otherwise.  Control can be direct or
        indirect.
        
        **Your licenses** are all the licenses granted to you for the
        software under these terms.
        
        **Use** means anything you do with the software requiring one
        of your licenses.
License-File: LICENSE
Keywords: azure,claude-code,key-vault,msal,plugin,sanoptis
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: <3.13,>=3.11
Requires-Dist: azure-identity<2,>=1.19
Requires-Dist: httpx<0.29,>=0.27
Requires-Dist: mcp<2,>=1.0
Requires-Dist: msal-extensions<2,>=1.2
Requires-Dist: pydantic<3,>=2
Provides-Extra: admin
Requires-Dist: alembic<2,>=1.13; extra == 'admin'
Requires-Dist: azure-identity<2,>=1.17; extra == 'admin'
Requires-Dist: azure-keyvault-secrets<5,>=4.8; extra == 'admin'
Requires-Dist: cryptography<45,>=42; extra == 'admin'
Requires-Dist: email-validator<3,>=2.0; extra == 'admin'
Requires-Dist: fastapi<0.120,>=0.115; extra == 'admin'
Requires-Dist: psycopg[binary]<4,>=3.1; extra == 'admin'
Requires-Dist: pyjwt[crypto]<3,>=2.8; extra == 'admin'
Requires-Dist: python-dotenv<2,>=1.0; extra == 'admin'
Requires-Dist: pyyaml<7,>=6.0; extra == 'admin'
Requires-Dist: reflex<0.9,>=0.8.28; extra == 'admin'
Requires-Dist: sqlmodel<0.1,>=0.0.22; extra == 'admin'
Description-Content-Type: text/markdown

# sanoptis-auth

Microsoft SSO + Azure Key Vault credential management for Sanoptis Claude Code plugins.

A single library that any Sanoptis plugin can use to fetch its API secrets, gated by the user's normal Microsoft sign-in. No more per-plugin dotfiles, chmod gymnastics, or hand-rolled credential bootstrap docs.

## Install

```shell
pip install sanoptis-auth
```

## What it does

- **Fetches secrets** from `kv-sanoptis-plugins` Azure Key Vault.
- **Authenticates the user** via Microsoft device-code flow on first use; caches the refresh token (90 days) for subsequent calls.
- **Surfaces the login prompt** as a clickable link in Claude chat (via the bundled `start_sanoptis_login` MCP tool) — no terminal required.
- **Enforces RBAC** at the Entra security-group level: a user with `sg-sanoptis-plugin-yokoy-users` can fetch yokoy secrets but not DATEV secrets, even from the same machine.

## Use it in a plugin

Add to your plugin's `requirements.txt`:

```
sanoptis-auth>=0.1.2
```

Replace any per-plugin credential loading with:

```python
from sanoptis_auth import get_secret

api_key = get_secret("yokoy--api-key")
api_secret = get_secret("yokoy--api-secret")
```

Register the MCP server alongside your own in `.mcp.json` so users can run `start_sanoptis_login` from chat:

```json
{
  "mcpServers": {
    "sanoptis-auth": {
      "command": "python",
      "args": ["-m", "sanoptis_auth.mcp_server"],
      "env": {
        "PYTHONPATH": "${CLAUDE_PLUGIN_DATA}/site-packages",
        "CLAUDE_PLUGIN_DATA": "${CLAUDE_PLUGIN_DATA}"
      }
    }
  }
}
```

## First-time login (end user)

1. Install the plugin in Claude Code or Cowork.
2. Use any tool that needs a secret. Claude will say something like *"Sanoptis login required — run the start_sanoptis_login tool."*
3. Run that tool. Claude shows you a clickable Microsoft device-code URL and a short code.
4. Click the link, sign in with your Sanoptis Microsoft account, paste the code.
5. Done — the next 90 days of plugin usage are automatic.

## Local dev override

For working offline or before your Entra group membership is set up:

```shell
export SANOPTIS_AUTH_LOCAL_OVERRIDE_FILE=~/.sanoptis-auth-overrides.env
cat > ~/.sanoptis-auth-overrides.env <<'EOF'
yokoy--api-key=dev-key
yokoy--api-secret=dev-secret
EOF
chmod 600 ~/.sanoptis-auth-overrides.env
```

`get_secret()` checks this file first when the env var is set.

## Admin app

The `sanoptis_admin/` package is a Reflex web app that is the primary tool for managing plugins, secrets, and group memberships. It replaces the old Terraform `plugins`-variable flow and the deprecated `rotate-secrets.yml` workflow.

Admins sign in with their Sanoptis Microsoft account and can:

- **Create plugins** — provisions `sg-sanoptis-plugin-<name>-users` in Entra.
- **Add/rotate/delete secrets** — writes to `kv-sanoptis-plugins` and assigns `Key Vault Secrets User` to the plugin group scoped to each specific secret.
- **Manage members** — resolves email → Entra user and adds/removes the group membership.
- **Audit** — every mutation is logged to the app's SQLite store with actor UPN, action, and target.

All operations run under the admin's own delegated token — no elevated service principal, no app-permission scopes. Non-admins (absent from `web.yaml`'s `admins:` list) see a "Not authorized" page.

Deployed to Azure Container Apps via `infra/azure/admin_app.tf`. Container image built and pushed by `.github/workflows/deploy-admin.yml` on pushes to `main` that touch `sanoptis_admin/**`.

Local dev:

```bash
cd sanoptis_admin
SANOPTIS_ADMIN_CONFIG=$(pwd)/web.yaml \
DATABASE_URL=sqlite:///admin.db \
ENTRA_CLIENT_SECRET=... \
SESSION_SECRET=$(python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())") \
pixi run -e admin reflex run
```

See `sanoptis_admin/web.yaml.example` for the config schema.

## Adding a new secret to the vault

Preferred: use the admin app — "Add/rotate secret" writes to Key Vault under the admin's own identity and records the rotation in the audit log.

Break-glass (CLI, for cases where the admin app is unavailable):

```shell
az login --tenant ea0bd7d3-b29f-47f4-aedc-da7b52a28ba0
az keyvault secret set --vault-name kv-sanoptis-plugins --name "myplugin--api-key" --value "..."
```

The `gha` service principal retains `Key Vault Secrets Officer` on the vault (see `infra/entra/gha_app.tf`) so an operator can rotate via `az` from a CI-OIDC-authenticated shell if needed.

## Adding a new plugin

Preferred: use the admin app — "Create plugin" provisions the Entra group and registers the plugin in the admin DB. "Add secret" then creates each secret and grants the group per-secret read access.

The old Terraform `plugins`-variable flow is retired; `infra/entra/groups.tf` and `role_assignments.tf` no longer exist. Group and per-secret RBAC lifecycle is owned by the admin app at runtime.

In your plugin code (unchanged):

```python
get_secret("myplugin--api-key")
```

## Repo layout

| Path | Purpose |
|---|---|
| `sanoptis_auth/` | Python package: MSAL + KV client + MCP server (consumed by plugins) |
| `sanoptis_auth/mcp_server/` | stdio MCP server exposing `start_sanoptis_login` and `whoami` |
| `sanoptis_admin/` | Reflex admin web app: plugins, secrets, memberships, audit log |
| `tests/` | pytest suite — `tests/` for the library, `tests/admin/` for the admin app |
| `infra/azure/` | CI-managed Terraform: Key Vault, Container App, ACR, Storage (File share for admin SQLite) |
| `infra/entra/` | Operator-managed Terraform: plugin public-client app, admin confidential app, GHA OIDC SP |
| `.github/workflows/` | CI, release, terraform plan/apply, admin-app deploy |

## Architecture decisions

- **Public client (no client secret).** The Entra app uses delegated access — secrets are read as the signed-in user, not as the app. Nothing to leak or rotate at the app level.
- **Per-plugin token cache.** Each plugin's `${CLAUDE_PLUGIN_DATA}` dir gets its own MSAL cache. The user signs in once per plugin; refresh tokens last 90 days. Trade-off: prevents one plugin from reading another's tokens.
- **Hand-rolled MSAL over `azure-identity.DefaultAzureCredential`.** `DefaultAzureCredential` tries 7 different credential chains and surfaces opaque error messages. We use `msal.PublicClientApplication` directly with a single device-code path — failures are explicit.
- **Group-scoped RBAC at the secret level.** A user with `sg-sanoptis-plugin-yokoy-users` cannot read DATEV secrets even if they install the DATEV plugin. RBAC is enforced by Azure Key Vault, not by application code.
- **Terraform over Bicep.** Reviewable, multi-cloud-portable, and the standard Sanoptis-BI infra tool. Runs via OIDC federated identity in CI — no service principal secret stored in GitHub Actions.

## Provisioning new infrastructure

See `infra/README.md` for the one-time Azure bootstrap procedure (Andrej / Matthias action).

## Source of truth

This repo is the only place credentials for Sanoptis Claude Code plugins should be managed. If you find a plugin reading secrets from a dotfile or environment variable, file an issue — it should be migrated to `sanoptis_auth.get_secret`.
