Metadata-Version: 2.4
Name: platform-buildingblocks
Version: 1.0.0
Summary: TimeChamp Platform Building Blocks for Python - Authentication, Multi-Tenancy, and Contracts
Author-email: Snovasys Technologies <dev@snovasys.com>
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: PyJWT[crypto]>=2.8.0
Requires-Dist: cryptography>=41.0.0
Requires-Dist: requests>=2.31.0
Requires-Dist: fastapi>=0.100.0 ; extra == "fastapi"
Requires-Dist: starlette>=0.27.0 ; extra == "fastapi"
Requires-Dist: flask>=2.3.0 ; extra == "flask"
Project-URL: Homepage, https://github.com/snovasys/timechamp-platform
Project-URL: Repository, https://github.com/snovasys/timechamp-platform
Provides-Extra: fastapi
Provides-Extra: flask
Provides-Extra: frappe

# Platform Building Blocks for Python

Python equivalents of the .NET `Platform.BuildingBlocks.*` NuGet packages for Python-based products on the TimeChamp Platform.

## Packages

| Python Module | .NET Equivalent | Purpose |
|---|---|---|
| `platform_buildingblocks.contracts` | `Platform.BuildingBlocks.Contracts` | Shared constants, models, claim types |
| `platform_buildingblocks.authentication` | `Platform.BuildingBlocks.Authentication` | Multi-tenant Keycloak JWT validation |
| `platform_buildingblocks.multitenancy` | `Platform.BuildingBlocks.MultiTenancy` | Tenant context resolution |

## Installation

```bash
pip install platform-buildingblocks
```

## Quick Start

### 1. Authentication (Validate JWT tokens)

```python
from platform_buildingblocks.authentication import PlatformAuth

# Initialize from config dict
auth = PlatformAuth.from_config({
    "keycloak_base_url": "http://localhost:8080",
    "expected_audiences": ["platform", "account", "myproduct-bff"],
})

# Validate a token and get user info
user = auth.get_current_user(request)
print(user.user_id)      # Keycloak user ID (sub claim)
print(user.tenant_id)    # Tenant ID
print(user.email)        # User email
print(user.is_admin)     # True if TenantAdmin or TenantOwner
```

### 2. Frappe Integration

```python
import frappe
from platform_buildingblocks.authentication import PlatformAuth

# Initialize from frappe site_config.json
auth = PlatformAuth.from_config(dict(frappe.conf))

@frappe.whitelist(allow_guest=False)
def my_api():
    user = auth.get_current_user_from_frappe()
    return {"userId": user.user_id, "tenantId": user.tenant_id}
```

### 3. Claim Extraction

```python
from platform_buildingblocks.authentication import (
    get_user_id, get_tenant_id, get_email, is_tenant_admin
)

claims = auth.validate_token(token_string)
user_id = get_user_id(claims)          # "550e8400-..."
tenant_id = get_tenant_id(claims)      # "123e4567-..."
email = get_email(claims)              # "user@example.com"
is_admin = is_tenant_admin(claims)     # True/False
```

### 4. Service-to-Service Tokens

```python
from platform_buildingblocks.authentication import ServiceAccountTokenProvider, KeycloakOptions

options = KeycloakOptions(
    base_url="http://localhost:8080",
    service_client_id="my-service-client",
    service_client_secret="secret",
)

provider = ServiceAccountTokenProvider(options)
token = provider.get_token("master")
headers = {"Authorization": f"Bearer {token.access_token}"}
```

## Configuration

### Frappe (site_config.json)

```json
{
    "keycloak_base_url": "http://localhost:8080",
    "expected_audiences": ["platform", "account", "myproduct-bff"],
    "keycloak_validation": {
        "validate_issuer": true,
        "validate_audience": true,
        "validate_lifetime": true
    }
}
```

### .NET-style (nested dict)

```python
config = {
    "Keycloak": {
        "BaseUrl": "http://localhost:8080",
        "ExpectedAudiences": ["platform", "account"],
        "TokenValidation": {
            "ValidateIssuer": True,
            "ValidateAudience": True,
        }
    }
}
auth = PlatformAuth.from_config(config)
```

