Metadata-Version: 2.4
Name: eficens-iam-sdk
Version: 0.4.2
Summary: Python SDK for the Eficens IAM service
Author: Eficens
License: MIT
Project-URL: Homepage, https://github.com/SheshadriChamarty/IAM
Project-URL: Repository, https://github.com/SheshadriChamarty/IAM
Project-URL: Issues, https://github.com/SheshadriChamarty/IAM/issues
Keywords: iam,auth,authorization,rbac,eficens
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: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: httpx<1.0,>=0.28

# eficens-iam-sdk (Python)

Client SDKs for the Eficens IAM API:

- **App SDK (`IamClient`)** — end-user signup/login, token refresh, introspect, authorization checks
- **Admin SDK (`IamAdminClient`)** — tenant/project management: roles, policies, vocabulary, principals, native users

**Package:** [pypi.org/project/eficens-iam-sdk](https://pypi.org/project/eficens-iam-sdk/)

## Install

```bash
pip install eficens-iam-sdk
```

Requires Python 3.10+.

## App SDK vs Admin SDK

| | App (`IamClient`) | Admin (`IamAdminClient`) |
|--|-------------------|--------------------------|
| Auth | Project API key + end-user access token | Tenant-admin / platform-admin JWT |
| Login | `POST /auth/token` | `POST /admin/login` |
| Use for | Runtime authn/authz in your application | Provisioning and RBAC management |
| Credentials | Keep API key on a trusted backend | Keep admin password on a trusted backend — never in a browser |

### Recommended flow

1. In the [IAM Console](https://iam.eficensittest.com): create a tenant (or sign up as owner), create a **project**, copy the **API key**, enable **native** (or Cognito/Clerk) auth.
2. From a trusted backend, use **Admin SDK** to create resources/actions, policies, roles, and assign roles to principals.
3. End users self-register via App SDK `signup` or hosted auth pages.
4. Your app runtime uses App SDK `login` + `check` / `batch_check`.

Tenant creation via API remains platform-admin only (`POST /tenants`). Self-service owners create their first tenant through console signup.

## Prerequisites (App SDK)

From the [IAM Console](https://iam.eficensittest.com):

1. Create a **project** and copy the **API key** (shown once; rotate later if needed).
2. Configure **Auth Settings** (`native`, `cognito`, or `clerk`).
3. Define resources/actions, roles, and policies (console or Admin SDK).

You need:

| Value | Example |
|-------|---------|
| API base URL | `https://api-iam.eficensittest.com/v1` |
| Project ID | UUID from the console |
| API key | `iam_…` (keep on the server — never embed in a public client) |

## Quick start (App SDK)

```python
from iam_sdk import IamClient, IamError

iam = IamClient(
    base_url="https://api-iam.eficensittest.com/v1",
    project_id="your-project-id",
    api_key="your-project-api-key",
)

tokens = iam.login("user@example.com", "secret")
profile = iam.introspect(tokens.access_token)
allowed = iam.check("todo.tasks.create", tokens.access_token)
```

## Quick start (Admin SDK)

```python
from iam_sdk import IamAdminClient, IamError

admin = IamAdminClient(base_url="https://api-iam.eficensittest.com/v1")
admin.login("owner@example.com", "admin-password")

admin.create_resource(tenant_id, project_id, "tasks")
admin.create_action(tenant_id, project_id, "create", resource="tasks")
admin.create_policy(tenant_id, project_id, "tasks-writer")
admin.add_policy_permission(
    tenant_id, project_id, "tasks-writer",
    resource="todo.tasks", action="create",
)
admin.create_role(tenant_id, project_id, "editor")
admin.set_role_policies(tenant_id, project_id, "editor", ["tasks-writer"])
admin.replace_principal_roles(tenant_id, project_id, principal_id, ["editor"])
```

## App constructor

```python
IamClient(
    base_url: str,          # IAM API root including /v1
    project_id: str,        # Project UUID
    api_key: str | None = None,  # Required for introspect / check / batch_check
    timeout: float = 10.0,
)
```

## Admin constructor

```python
IamAdminClient(
    base_url: str,       # IAM API root including /v1
    timeout: float = 15.0,
)
# Then admin.login(email, password) or admin.set_token(jwt)
```

## Headers

### App SDK

| Header | When |
|--------|------|
| `X-Api-Key` | introspect, check, batch_check |
| `Authorization: Bearer <access_token>` | introspect, check, batch_check |
| `X-Project-Id` | check, batch_check |

Login / refresh / ID-token exchange / signup / password helpers do **not** require the API key.

### Admin SDK

| Header | When |
|--------|------|
| `Authorization: Bearer <admin_token>` | All management calls after `login` / `set_token` |

## Authentication (App SDK)

### Native signup

Creates a project native user and sends a verification email. Password min length is **12**.

```python
iam.signup("user@example.com", "long-enough-password")
iam.verify_email(token_from_email)
# or
iam.resend_verification("user@example.com")
```

Native users must verify email before `login` succeeds.

### Native password login

```python
tokens = iam.login(email, password)
# TokenResponse(access_token=..., refresh_token=..., token_type="bearer")
```

Access tokens expire (default **15 minutes**). Store the refresh token securely and refresh before expiry.

### Refresh

```python
refreshed = iam.refresh(tokens.refresh_token)
```

### Forgot / reset password

```python
iam.forgot_password("user@example.com")
iam.reset_password(token_from_email, "new-long-password")
```

### Cognito / OIDC ID token exchange

After the user signs in with Cognito (or another OIDC IdP configured on the project):

```python
tokens = iam.exchange_id_token(id_token)
```

### Introspect

```python
profile = iam.introspect(tokens.access_token)
# dict with active, principal_id, tenant_id, project_id, email, roles, permissions
```

## Authorization (App SDK)

Permission strings follow `{project_slug}.{resource}.{action}` (e.g. `todo.tasks.create`).

### Single check

```python
allowed = iam.check("todo.tasks.create", tokens.access_token)
if not allowed:
    raise PermissionError("Forbidden")
```

### Batch check

```python
result = iam.batch_check(
    [
        {"permission": "todo.tasks.read"},
        {"permission": "todo.tasks.delete"},
    ],
    tokens.access_token,
)
# result["results"] → list of {resource, action, allowed}
```

## Admin management APIs

After `admin.login(...)`:

| Area | Methods |
|------|---------|
| Session | `login`, `set_token`, `me` |
| Tenants / projects | `list_tenants`, `list_projects`, `get_auth_config`, `update_auth_config` |
| Vocabulary | `list_resources`, `create_resource`, `update_resource`, `delete_resource`, `list_actions` (optional resource filter), `create_action` (requires resource / resource_id), `update_action`, `delete_action` |
| Roles | `list_roles`, `create_role`, `get_role`, `delete_role`, `set_role_policies`, `add_role_policies`, `remove_role_policy` |
| Policies | `list_policies`, `get_policy`, `create_policy`, `update_policy` (description and/or full permissions replace), `delete_policy`, `add_policy_permission`, `remove_policy_permission`, `set_policy_permissions` |
| Principals | `list_principals`, `get_principal`, `get_principal_roles`, `get_principal_permissions`, `replace_principal_roles`, `add_principal_roles`, `remove_principal_role` |
| Native users | `list_native_users`, `update_native_user`, `reset_native_user_password` |
| Invitations | `list_invitations`, `create_invitation`, `resend_invitation`, `revoke_invitation` |

There is no admin “create user with password” API. Prefer **invitations** (`create_invitation` with roles) so users onboard with roles on accept. Self-serve signup via App SDK `signup` / hosted sign-up still works. Admins can also list users, update status / email_verified, and trigger password-reset emails.

## Hosted auth vs headless

### Hosted (recommended for browser apps)

Do **not** put the API key or admin credentials in the browser. Redirect users to IAM hosted pages:

```
https://iam.eficensittest.com/auth/sign-in?project_id=<PROJECT_UUID>&redirect_uri=<YOUR_APP_URL>&state=<OPTIONAL>
```

| Path | Purpose |
|------|---------|
| `/auth/sign-in` | Native login |
| `/auth/sign-up` | Native signup |
| `/auth/verify-email` | Email verification |
| `/auth/forgot-password` | Request password reset |
| `/auth/reset-password` | Set new password |
| `/auth/accept-invite` | Accept project invitation |

Query params for sign-in / sign-up:

- `project_id` (required)
- `redirect_uri` (optional) — after login, IAM redirects to  
  `{redirect_uri}?access_token=...&refresh_token=...&state=...`
- `state` (optional) — echoed back on redirect

Use the returned `access_token` with your **backend** (which holds the API key) for `introspect` / `check`.

### Headless (backend / server)

Call `iam.login()` / `iam.signup()` from your server. Keep the project API key and any admin credentials in environment variables.

## Errors

Failed requests raise `IamError`:

```python
from iam_sdk import IamClient, IamError

try:
    iam.login(email, password)
except IamError as err:
    print(err.status_code, err, err.detail)
    raise
```

### Common status codes

| Status | Meaning |
|--------|---------|
| 401 | Invalid API key or access/refresh/admin token |
| 403 | Not allowed, email not verified, or tenant/user disabled |
| 429 | Rate limited (login endpoints) |

## End-to-end example (FastAPI)

```python
import os
from fastapi import Depends, FastAPI, Header, HTTPException
from iam_sdk import IamClient, IamError

iam = IamClient(
    base_url=os.environ["IAM_API_URL"],
    project_id=os.environ["IAM_PROJECT_ID"],
    api_key=os.environ["IAM_API_KEY"],
)

app = FastAPI()


def bearer_token(authorization: str | None = Header(default=None)) -> str:
    if not authorization or not authorization.lower().startswith("bearer "):
        raise HTTPException(status_code=401, detail="Missing bearer token")
    return authorization.split(" ", 1)[1]


@app.post("/login")
def login(body: dict):
    try:
        tokens = iam.login(body["email"], body["password"])
    except IamError as err:
        raise HTTPException(status_code=err.status_code or 400, detail=str(err)) from err
    return {
        "access_token": tokens.access_token,
        "refresh_token": tokens.refresh_token,
        "token_type": tokens.token_type,
    }


@app.get("/todos")
def list_todos(token: str = Depends(bearer_token)):
    if not iam.check("todo.tasks.read", token):
        raise HTTPException(status_code=403, detail="Forbidden")
    return []
```

## App API reference

| Method | Description |
|--------|-------------|
| `signup(email, password)` | Native signup → verification email |
| `verify_email(token)` | Confirm email |
| `resend_verification(email)` | Resend verification email |
| `forgot_password(email)` | Request password reset email |
| `reset_password(token, new_password)` | Set new password from reset token |
| `login(email, password)` | Native password grant → `TokenResponse` |
| `refresh(refresh_token)` | Refresh access token |
| `exchange_id_token(id_token)` | Cognito/OIDC ID token → IAM tokens |
| `introspect(access_token)` | Profile, roles, permissions (`dict`) |
| `check(permission, access_token)` | Single permission → `bool` |
| `batch_check(checks, access_token)` | Multiple checks → `dict` with `results` |

## Support

Email: [sheshadri.c@eficens.com](mailto:sheshadri.c@eficens.com)

Include your project slug and approximate time of the issue. Do not send API keys or passwords.
