Metadata-Version: 2.4
Name: applica-iam-client
Version: 0.1.1
Summary: Python client for the Applica IAM service
Author: Applica Software Guru
License-Expression: MIT
License-File: LICENSE
Keywords: auth,client,iam
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: pycryptodome>=3.20
Requires-Dist: pydantic>=2.0
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.8; extra == 'dev'
Description-Content-Type: text/markdown

# applica-iam-client

Python client for the Applica IAM service. Async-only, type-safe, mirroring the TypeScript
`@applica-software-guru/iam-client`.

| Info       | Details                                         |
|------------|-------------------------------------------------|
| Version    | 0.1.*                                           |
| Author     | Roberto Conte Rosito (Applica)                  |
| Repository | https://bitbucket.org/applicaguru/iam-client-py |
| License    | MIT                                             |

## Installation

```bash
pip install applica-iam-client
```

## Quick Start

```python
import asyncio

from iam_client import create_iam_client


async def main():
    async with create_iam_client(api_url="https://server.com/api", api_key="your-system-api-key") as iam:
        await iam.auth.login({"username": "user@test.com", "password": "secret"})
        tenants = await iam.tenants.search({"keyword": "test"})
        user = await iam.users.get_by_id("user-id")
        await iam.roles.register({"code": "EDITOR", "name": "Editor", "permissions": ["read", "write"]})


asyncio.run(main())
```

## Authentication

The IAM backend uses three security chains with different authentication requirements:

| Endpoints                                               | Auth required               | Notes                                                                                                |
|---------------------------------------------------------|-----------------------------|------------------------------------------------------------------------------------------------------|
| `/auth/**`                                              | None                        | Public endpoints (login, register, activate, recover)                                                |
| `/tenants/**`                                           | **System API key only**     | Requires `x-api-key` header matching the server's `iam.api-key`. Bearer tokens are **not** accepted. |
| `/users/**`, `/roles/**`, `/projects/**`, `/devices/**` | Bearer token **or** API key | Any of these methods works: JWT from login, system API key, or tenant API key                        |

### How authentication works in the client

When you set `api_key` on the client, the `x-api-key` header is sent on **every** request. When a user is logged in, the
`Authorization: Bearer <token>` header is also sent. The API key takes priority if both are present.

**Scenario 1 — Server-side / admin scripts (no user login):**

Use the system API key. This grants `ROLE_SYSTEM` and gives access to all endpoints including `/tenants/**`:

```python
iam = create_iam_client(api_url="https://server.com/api", api_key="your-system-api-key")

# No login needed — the API key authenticates all requests
await iam.tenants.register({"code": "new-tenant", "name": "New Tenant"})
await iam.users.search({})
await iam.roles.register({"code": "EDITOR", "name": "Editor"})
```

**Scenario 2 — Application with logged-in user:**

After login, the Bearer token authenticates requests to `/users/**`, `/roles/**`, `/projects/**`, `/devices/**`. No API
key needed for these endpoints:

```python
iam = create_iam_client(api_url="https://server.com/api")

await iam.auth.login({"username": "admin@test.com", "password": "secret"})

# These work with the Bearer token from login
await iam.users.search({})
await iam.roles.register({"code": "EDITOR", "name": "Editor"})
await iam.projects.search({})

# This will FAIL — /tenants/** requires the system API key, not a Bearer token
await iam.tenants.search({})  # 403
```

**Scenario 3 — Application that also needs tenant management:**

Provide both. The API key handles `/tenants/**`, the Bearer token handles everything else (or the API key handles
everything):

```python
iam = create_iam_client(api_url="https://server.com/api", api_key="your-system-api-key")

await iam.auth.login({"username": "admin@test.com", "password": "secret"})

# All endpoints work
await iam.tenants.search({})
await iam.users.search({})
```

### What is the system API key and where to find it

The system API key is configured server-side in the IAM backend's `application.yaml`:

```yaml
iam:
  api-key: "your-secret-key-here"
```

This is the master key that grants `ROLE_SYSTEM` access. It is set by the server administrator and should be treated as
a secret. The `GodAuthenticationFilter` on the backend reads the `x-api-key` header from incoming requests and compares
it against this configured value.

There is no API endpoint to generate or rotate this key — it is a static configuration value managed at deployment time.
To change it, update the `iam.api-key` property in the server configuration and restart the IAM service.

> **Important:** The system API key gives unrestricted access to all endpoints. Never expose it in client-side code in
> production. It is intended for server-to-server communication, admin scripts, and development environments.

## Architecture

```
IamClient
  ├── auth: AuthService          → /auth/*
  ├── users: UserService         → /users/*
  ├── tenants: TenantService     → /tenants/*
  ├── projects: ProjectService   → /projects/*
  ├── roles: RoleService         → /roles/*
  └── devices: DeviceService     → /devices/*
```

## Documentation

| Topic                                                 | Description                                       |
|-------------------------------------------------------|---------------------------------------------------|
| [Getting Started](docs/getting-started.md)            | Installation, configuration, async usage          |
| [Architecture](docs/architecture.md)                  | IamClient structure, HttpClient, project layout   |
| [AuthService](docs/auth-service.md)                   | Authentication, session, impersonation, PIN login |
| [UserService](docs/user-service.md)                   | User CRUD, roles, tenant/project assignment       |
| [TenantService](docs/tenant-service.md)               | Tenant CRUD (requires system API key)             |
| [ProjectService](docs/project-service.md)             | Project CRUD within tenants                       |
| [RoleService](docs/role-service.md)                   | Role and permission management                    |
| [DeviceService](docs/device-service.md)               | Device management for PIN authentication          |
| [Error Handling & Pagination](docs/error-handling.md) | FailureResponse, error codes, pagination          |
| [Storage](docs/storage.md)                            | MemoryStorage, custom IStorage                    |
| [Type Reference](docs/types.md)                       | All Pydantic models and DTOs                      |

## Scripts

```bash
pip install -e ".[dev]"    # Install in dev mode
ruff check src/            # Lint
pytest tests/ -v           # Run tests (requires IAM server)
python -m build            # Build package
```

## License

MIT
