Metadata-Version: 2.4
Name: devkanan
Version: 0.2.2
Summary: Cliente Python para DevKanan — validación local + API online de licencias firmadas RSA
Author-email: Vilchis <vilchislalo98@gmail.com>
License: Apache-2.0
Project-URL: Homepage, https://devkanan.dev
Keywords: license,licensing,rsa,drm,devkanan,kanan
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: OS Independent
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: cryptography>=41.0.0
Requires-Dist: requests>=2.28.0
Dynamic: license-file

# devkanan

[![PyPI version](https://img.shields.io/pypi/v/devkanan.svg)](https://pypi.org/project/devkanan/)
[![Python](https://img.shields.io/pypi/pyversions/devkanan.svg)](https://pypi.org/project/devkanan/)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)

Lightweight Python client to activate and validate RSA-signed licenses issued by the **[DevKanan](https://devkanan.dev)** platform or an on-premise **DevKananServer**.

## Installation

```bash
pip install devkanan
```

## Usage

### Online activation

```python
from devkanan import Key, Helpers

result, msg = Key.activate(
    token=ACCESS_TOKEN,
    rsa_pub_key=RSA_PUBLIC_KEY,
    product_id=100000,
    key="ABCD-EFGH-IJKL-MNOP",
    machine_code=Helpers.GetMachineCode(v=2),
    # floating_time_interval=300,  # optional floating lease
    server_url="https://devkanan.dev",  # optional
)

if result is None:
    print(f"Activation failed: {msg}")
    exit(1)

if result.is_on_right_machine(Helpers.GetMachineCode(v=2)) and result.has_not_expired():
    print("License valid — app can start")
```

### Offline validation (signed `.dat` file)

```python
from devkanan import LicenseKey, Helpers

RSA_PUB_KEY = """-----BEGIN PUBLIC KEY-----
... your tenant's RSA public key ...
-----END PUBLIC KEY-----"""

with open("license.dat", encoding="utf-8-sig") as f:
    lk = LicenseKey.load_from_string(RSA_PUB_KEY, f.read(), max_age_days=1)

if lk is None:
    print("Invalid license (corrupt signature, expired or blocked)")
    exit(1)

if not lk.is_on_right_machine(Helpers.GetMachineCode(v=2)):
    print("This machine is not authorized")
    exit(1)

# Features
if lk.F1:
    enable_pro_features()
```

### Usage credits

```python
from devkanan import Credits

# Consume credits when a premium feature is used
ok, balance, msg = Credits.record(
    token=ACCESS_TOKEN, product_id=100000, key=LICENSE_KEY,
    amount=10, feature="export_pdf",
)

# Check current balance
enabled, balance, msg = Credits.balance(
    token=ACCESS_TOKEN, product_id=100000, key=LICENSE_KEY,
)
```

## API

### `Helpers.GetMachineCode(v=2)`

Generates a unique machine code based on hardware properties. `v=2` (default) uses BIOS UUID via PowerShell on Windows or `/etc/machine-id` on Linux. Produces the same hash as the Cryptolens SDK.

### `LicenseKey.load_from_string(rsa_pub_key, content, max_age_days=0)`

Parses a `.dat`, verifies the RSA signature and freshness.

| Parameter | Type | Description |
|---|---|---|
| `rsa_pub_key` | str | XML (.NET) or PEM (standard). Auto-detected. |
| `content` | str | `.dat` file contents |
| `max_age_days` | int | Max days since `signDate` before re-validation is required. 0 = no limit. |

Returns a `LicenseKey` object or `None` if any check fails.

### `LicenseKey` methods

- `is_on_right_machine(machine_code)` — verifies the machine is authorized
- `has_not_expired()` — verifies `expires > now`
- `has_feature(n)` — shortcut for `getattr(lk, f'f{n}')` (n = 1..8)

### `LicenseKey` attributes

| Attribute | Type |
|---|---|
| `product_id`, `id`, `key`, `notes` | basic data |
| `created`, `expires`, `sign_date` | unix timestamps |
| `period`, `max_no_of_machines` | integers |
| `F1`..`F8`, `block`, `trial_activation` | flags |
| `activated_machines` | List[ActivatedMachine] |
| `data_objects` | List[DataObject] |
| `customer` | Customer or None |
| `offline_mode` | "FloatingLease" / "FloatingManual" / "Locked" |

## Comparison with Cryptolens SDK

| | Cryptolens SDK | devkanan |
|---|---|---|
| Online activation | ✅ | ✅ |
| Offline `.dat` / `.skm` validation | ✅ | ✅ |
| `Helpers.GetMachineCode` | ✅ | ✅ (same hash) |
| `LicenseKey.load_from_string` | ✅ | ✅ |
| Size | ~2 MB with deps | ~10 KB |
| Dependencies | `pycryptodome`, `requests` | `cryptography`, `requests` |

## Documentation

- [DevKanan docs](https://devkanan.dev/docs)
- [API reference](https://devkanan.dev/docs/en/api)
- [Offline mode guide](https://devkanan.dev/docs/en/offline)

## License

[Apache 2.0](LICENSE). Partially based on Cryptolens Python SDK (also Apache 2.0).
