Metadata-Version: 2.4
Name: in-layers-auth
Version: 1.2.0
Summary: Minimal REST authentication client for In Layers (Python).
License: GPLv3
License-File: LICENSE
Keywords: layers,python,in-layers,auth,rest,client
Author: Mike Cornwell
Author-email: mike@mikecornwell.com
Requires-Python: >=3.11,<4.0
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: 3.15
Requires-Dist: in-layers-core (>=1.0.0,<2.0.0)
Description-Content-Type: text/markdown

# In Layers Auth Client

`in-layers-auth` is the Python authentication client for In Layers systems (`in_layers.auth.client`), matching the TypeScript `@node-in-layers/auth/client` contract and lifecycle.

## Install

```bash
pip install in-layers-auth
```

## Uniform Lifecycle & Contract

Every authentication mechanism goes through `login(props)` and returns the exact same shape:

```python
{
    "user": {
        "id": "...",
        "email": "...",
        "firstName": "...",
        "lastName": "...",
        "enabled": True,
    },
    "token": "<jwt>",
    "refreshToken": "<refresh-token>",  # optional for oauth2 client_credentials
    "loginApproach": "...",
}
```

- **`login(props)`**:
  - Pass explicit credentials at runtime (`basicAuth`, `apiKeyAuth`, `oidcAuth`, or `sharedSecretAuth`), **or**
  - Pass an empty dict `login({})` to authenticate using `in_layers_auth_client.credentials` from your system config.
- **`get_auth({})`**:
  - Returns `{"key": "<jwt>", "header": "Authorization", "formatter": ...}` for use by your HTTP / OpenAPI / Fern SDK.
  - If not yet logged in and `credentials` are configured in config, `get_auth({})` automatically calls `login({})`.
  - If the current token is near expiry, `get_auth({})` automatically refreshes it (via `refresh({})` when a `refreshToken` is present, or `login({})` for stateless/OAuth2 credentials).

## Configure a Consuming System

Register `in_layers.auth.client` as a domain and configure `in_layers_auth_client`:

```python
from box import Box
from in_layers.auth import client
from in_layers.core import LogFormat, LogLevelNames


def get_config():
    return Box(
        environment="base",
        system_name="example-worker",
        in_layers_core=Box(
            logging=Box(
                log_level=LogLevelNames.info,
                log_format=LogFormat.simple,
            ),
            layer_order=["services", "features"],
            domains=[client],
        ),
        in_layers_auth_client=Box(
            base_url="https://api.example.com",
            credentials=Box(
                sharedSecretAuth=Box(
                    secret="shared-secret",
                    serviceName="worker",
                ),
            ),
        ),
    )
```

## Configuration Examples by Auth Mechanism

All configured credentials live under `in_layers_auth_client.credentials`. Switching a server-to-server connection from shared secret to OAuth2 (or API key / basic auth) requires **only** changing `credentials` in config—application code stays identical.

### 1. Shared Secret (`sharedSecretAuth`)

```python
in_layers_auth_client=Box(
    base_url="https://api.example.com",
    credentials=Box(
        sharedSecretAuth=Box(
            secret="shared-secret",
            serviceName="worker",
        ),
    ),
)
```

### 2. OAuth2 Client Credentials (`oauth2`)

```python
in_layers_auth_client=Box(
    base_url="https://api.example.com",
    credentials=Box(
        oauth2=Box(
            token_url="https://identity.example.com/oauth/token",
            client_id="worker",
            client_secret="secret",
            scopes=["orders.read"],
        ),
    ),
)
```

### 3. API Key (`apiKeyAuth`)

```python
in_layers_auth_client=Box(
    base_url="https://api.example.com",
    credentials=Box(
        apiKeyAuth=Box(
            key="api-key-value",
        ),
    ),
)
```

### 4. Basic Auth (`basicAuth`)

```python
in_layers_auth_client=Box(
    base_url="https://api.example.com",
    credentials=Box(
        basicAuth=Box(
            identifier="person@example.com",
            password="password",
        ),
    ),
)
```

### 5. OIDC Token (`oidcAuth`)

```python
in_layers_auth_client=Box(
    base_url="https://api.example.com",
    credentials=Box(
        oidcAuth=Box(
            token="oidc-id-or-access-token",
        ),
    ),
)
```

## Using with an External SDK/Client (Example with Fern)

In your domain's `services.py`, pass a callable to your Fern SDK's `token` parameter that reads `get_auth({})` from `in_layers.auth.client`:

```python
from in_layers.auth import AuthNamespace
from my_fern_sdk import MyFernClient


class MyDomainServices:
    def __init__(self, context):
        self.__context = context
        self.__client = None

    def _get_token(self) -> str:
        auth_service = self.__context.services[AuthNamespace.client.value]
        auth = auth_service.get_auth({})
        if not auth:
            raise RuntimeError("Unable to resolve authentication token")
        return auth["key"]

    @property
    def client(self) -> MyFernClient:
        if self.__client is None:
            self.__client = MyFernClient(
                base_url=self.__context.config.my_domain.base_url,
                token=self._get_token,
            )
        return self.__client


def create(context) -> MyDomainServices:
    return MyDomainServices(context)
```

