Metadata-Version: 2.4
Name: luduvo.py
Version: 0.1.13
Summary: Luduvo Python library
Author: NovaDev
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: requests

# luduvo.py

A Python library for interacting with the Luduvo API.

It provides a simple interface for Luduvo API requests, OAuth 2.0 authentication, PKCE, OAuth application management, token exchange, and authenticated user information.

## Features

- Luduvo API client
- Bearer token authentication
- OAuth application creation
- OAuth application listing
- OAuth application deletion
- OAuth 2.0 authorization
- PKCE authentication
- Authorization code exchange
- Authenticated user information
- Simple Python API
- Automatic JSON request handling

## Installation

Install the package from PyPI:

    python -m pip install luduvo.py

Upgrade to the latest version:

    python -m pip install --upgrade luduvo.py

Import the package:

    from luduvo import Luduvo

## Requirements

- Python 3.9+
- requests

`requests` is installed automatically when installing the package.

## Basic Usage

Create a client without authentication:

    from luduvo import Luduvo

    client = Luduvo()

Or provide a Luduvo API token:

    from luduvo import Luduvo

    client = Luduvo("YOUR_LUDUVO_TOKEN")

The API base URL is:

    https://api.luduvo.com

When a token is provided, authenticated requests automatically use:

    Authorization: Bearer YOUR_LUDUVO_TOKEN

# OAuth

`luduvo.py` provides helpers for the Luduvo OAuth 2.0 authorization flow with PKCE.

The OAuth flow is:

    Create OAuth Application
             │
             ▼
    Generate PKCE verifier
             │
             ▼
    Generate PKCE challenge
             │
             ▼
    Create authorization URL
             │
             ▼
    User signs into Luduvo
             │
             ▼
    User authorizes application
             │
             ▼
    Luduvo redirects to callback
             │
             ▼
    Receive authorization code
             │
             ▼
    Exchange code + verifier
             │
             ▼
    Receive access token
             │
             ▼
    Request /oauth/userinfo

# OAuth Application Management

## Creating an OAuth Application

OAuth applications can be created through the Luduvo API.

    from luduvo import Luduvo

    client = Luduvo("YOUR_LUDUVO_TOKEN")

    app = client.oauth.create(
        name="My Luduvo App",
        description="An application using Luduvo OAuth.",
        redirect_uris=[
            "http://localhost:3000/callback"
        ],
        is_confidential=True
    )

    print(app)

A response will contain information similar to:

    {
        "id": 5,
        "client_id": "ldv_b748212bcc097120d2bfac589a6977dd",
        "name": "My Luduvo App",
        "description": "An application using Luduvo OAuth.",
        "redirect_uris": [
            "http://localhost:3000/callback"
        ],
        "is_confidential": true,
        "created_at": 1790751617,
        "updated_at": 1790751617
    }

## create_oauth()

`create_oauth()` is an alias for `create()`.

    from luduvo import Luduvo

    client = Luduvo("YOUR_LUDUVO_TOKEN")

    app = client.oauth.create_oauth(
        name="My Luduvo App",
        description="An application using Luduvo OAuth.",
        redirect_uris=[
            "http://localhost:3000/callback"
        ],
        is_confidential=True
    )

## Listing OAuth Applications

List all OAuth applications belonging to the authenticated account.

    from luduvo import Luduvo

    client = Luduvo("YOUR_LUDUVO_TOKEN")

    apps = client.oauth.list()

    for app in apps:
        print("Name:", app["name"])
        print("Client ID:", app["client_id"])
        print()

## Deleting an OAuth Application

Delete an OAuth application using its `client_id`.

    from luduvo import Luduvo

    client = Luduvo("YOUR_LUDUVO_TOKEN")

    client.oauth.delete(
        "ldv_b748212bcc097120d2bfac589a6977dd"
    )

The numeric application ID should not be used for deletion.

Use:

    client_id

instead of:

    id

# PKCE

PKCE values can be generated with:

    from luduvo import Luduvo

    client = Luduvo()

    pkce = client.oauth.create_pkce()

    print("Code verifier:", pkce["code_verifier"])
    print("Code challenge:", pkce["code_challenge"])

The returned object contains:

    {
        "code_verifier": "...",
        "code_challenge": "..."
    }

The library generates the challenge using SHA-256 and Base64 URL encoding.

The authorization request uses:

    code_challenge_method=S256

The `code_verifier` must be kept until the authorization code is exchanged for an access token.

# Authorization

## Creating an Authorization URL

Generate a PKCE pair:

    pkce = client.oauth.create_pkce()

Then create the authorization URL:

    auth_url, state = client.oauth.authorize_url(
        client_id="ldv_b748212bcc097120d2bfac589a6977dd",
        redirect_uri="http://localhost:3000/callback",
        code_challenge=pkce["code_challenge"]
    )

    print(auth_url)
    print(state)

The generated authorization URL contains parameters such as:

    response_type=code
    client_id=...
    redirect_uri=...
    scope=identify
    code_challenge=...
    code_challenge_method=S256
    state=...

The `state` value should be stored and verified when the OAuth callback is received.

## Authorization URL Parameters

`authorize_url()` accepts:

    client.oauth.authorize_url(
        client_id,
        redirect_uri,
        code_challenge,
        state=None,
        scope="identify",
        prompt=None
    )

### client_id

The OAuth application's client ID.

Example:

    client_id="ldv_b748212bcc097120d2bfac589a6977dd"

### redirect_uri

The callback URL registered with the OAuth application.

Example:

    redirect_uri="http://localhost:3000/callback"

The URI must match one of the application's registered redirect URIs.

### code_challenge

The PKCE challenge generated by:

    pkce = client.oauth.create_pkce()

    pkce["code_challenge"]

### state

An optional state value.

If omitted, the library automatically generates a secure random state.

Example:

    auth_url, state = client.oauth.authorize_url(
        client_id="ldv_b748212bcc097120d2bfac589a6977dd",
        redirect_uri="http://localhost:3000/callback",
        code_challenge=pkce["code_challenge"]
    )

### scope

The OAuth scope.

The default scope is:

    identify

Example:

    auth_url, state = client.oauth.authorize_url(
        client_id="ldv_b748212bcc097120d2bfac589a6977dd",
        redirect_uri="http://localhost:3000/callback",
        code_challenge=pkce["code_challenge"],
        scope="identify"
    )

### prompt

An optional prompt value can be passed to the authorization endpoint.

    auth_url, state = client.oauth.authorize_url(
        client_id="ldv_b748212bcc097120d2bfac589a6977dd",
        redirect_uri="http://localhost:3000/callback",
        code_challenge=pkce["code_challenge"],
        prompt="login"
    )

# Token Exchange

After the user authorizes the application, Luduvo redirects to the registered callback URL.

The callback contains an authorization code.

Exchange the code for an access token:

    token = client.oauth.token(
        client_id="ldv_b748212bcc097120d2bfac589a6977dd",
        client_secret="YOUR_CLIENT_SECRET",
        redirect_uri="http://localhost:3000/callback",
        code="AUTHORIZATION_CODE",
        code_verifier=pkce["code_verifier"]
    )

    print(token)

The token request sends:

    {
        "grant_type": "authorization_code",
        "client_id": "...",
        "client_secret": "...",
        "redirect_uri": "...",
        "code": "...",
        "code_verifier": "..."
    }

The authorization code can only be used according to the OAuth server's code expiration and reuse rules.

# User Information

Once an OAuth access token has been received, user information can be requested using:

    user = client.oauth.userinfo(
        access_token=token["access_token"]
    )

    print(user)

A userinfo response looks similar to:

    {
        "avatar_url": "https://assets.luduvo.com/headshots/5/0197c88cd526d6a5f3d7ead056d1f12fba6df6a1b2dd9454fe022a978ab88970.png",
        "created_at": 1774378965,
        "display_name": "dargy",
        "id": 5,
        "sub": "5",
        "username": "dargs"
    }

The available fields include:

| Field | Description |
| --- | --- |
| `id` | Luduvo user ID |
| `sub` | OAuth subject identifier |
| `username` | Luduvo username |
| `display_name` | User display name |
| `avatar_url` | User avatar URL |
| `created_at` | Account creation timestamp |

# Using the Access Token

The access token can be passed directly to `userinfo()`:

    user = client.oauth.userinfo(
        access_token=token["access_token"]
    )

Alternatively, assign the access token to the client:

    client.token = token["access_token"]

Then:

    user = client.oauth.userinfo()

This allows `userinfo()` to use the client's stored token.

# Complete OAuth Example

The following example demonstrates the complete OAuth flow.

    from luduvo import Luduvo

    client = Luduvo("YOUR_LUDUVO_TOKEN")

    # Generate PKCE values
    pkce = client.oauth.create_pkce()

    # Create the authorization URL
    auth_url, state = client.oauth.authorize_url(
        client_id="YOUR_CLIENT_ID",
        redirect_uri="http://localhost:3000/callback",
        code_challenge=pkce["code_challenge"]
    )

    print("Open this URL in your browser:")
    print(auth_url)

    # After the user authorizes the application,
    # your callback receives the authorization code.

    code = input("Authorization code: ")

    # Exchange the authorization code for an access token
    token = client.oauth.token(
        client_id="YOUR_CLIENT_ID",
        client_secret="YOUR_CLIENT_SECRET",
        redirect_uri="http://localhost:3000/callback",
        code=code,
        code_verifier=pkce["code_verifier"]
    )

    print("Access token received.")

    # Request user information
    user = client.oauth.userinfo(
        access_token=token["access_token"]
    )

    print("User ID:", user["id"])
    print("Username:", user["username"])
    print("Display Name:", user["display_name"])

# Flask Callback Example

A minimal Flask application can be used to handle the OAuth callback.

    from flask import Flask, redirect, request

    from luduvo import Luduvo

    app = Flask(__name__)

    client = Luduvo("YOUR_LUDUVO_TOKEN")

    pkce = client.oauth.create_pkce()

    CLIENT_ID = "YOUR_CLIENT_ID"
    CLIENT_SECRET = "YOUR_CLIENT_SECRET"
    REDIRECT_URI = "http://127.0.0.1:3000/callback"

    auth_url, state = client.oauth.authorize_url(
        client_id=CLIENT_ID,
        redirect_uri=REDIRECT_URI,
        code_challenge=pkce["code_challenge"],
        state=state
    )

    @app.route("/")
    def index():
        return f'<a href="{auth_url}">Login with Luduvo</a>'

    @app.route("/callback")
    def callback():
        code = request.args.get("code")
        returned_state = request.args.get("state")

        if not code:
            return "Missing authorization code.", 400

        if returned_state != state:
            return "Invalid state.", 400

        token = client.oauth.token(
            client_id=CLIENT_ID,
            client_secret=CLIENT_SECRET,
            redirect_uri=REDIRECT_URI,
            code=code,
            code_verifier=pkce["code_verifier"]
        )

        user = client.oauth.userinfo(
            access_token=token["access_token"]
        )

        return {
            "user": user,
            "token": token
        }

    if __name__ == "__main__":
        app.run(
            host="127.0.0.1",
            port=3000,
            debug=True
        )

For a real application, store the PKCE verifier and OAuth state in the user's server-side session rather than using global variables.

# API Endpoints

The library currently uses these Luduvo OAuth endpoints:

| Method | Endpoint | Purpose |
| --- | --- | --- |
| `POST` | `/oauth/apps` | Create OAuth application |
| `GET` | `/oauth/apps` | List OAuth applications |
| `DELETE` | `/oauth/apps/{client_id}` | Delete OAuth application |
| `GET` | `/oauth/authorize` | Authorize OAuth application |
| `POST` | `/oauth/token` | Exchange authorization code |
| `GET` | `/oauth/userinfo` | Get authenticated user |

The complete base URL is:

    https://api.luduvo.com

# Authentication

Application-management requests use a Luduvo API token:

    Authorization: Bearer YOUR_LUDUVO_TOKEN

OAuth userinfo requests use an OAuth access token:

    Authorization: Bearer YOUR_ACCESS_TOKEN

The library automatically sets the appropriate headers for requests made through the client.

# Error Handling

API errors raise a `RuntimeError`.

Example:

    from luduvo import Luduvo

    client = Luduvo("YOUR_LUDUVO_TOKEN")

    try:
        apps = client.oauth.list()
    except RuntimeError as error:
        print(error)

An error contains the HTTP status code and the response returned by the API.

For example:

    Luduvo API returned 401: {'error': 'unauthorized'}

# Security

Never expose your Luduvo API token or OAuth client secret in frontend JavaScript, public repositories, or client-side applications.

Store secrets in environment variables or another secure secret store.

For example:

    import os

    from luduvo import Luduvo

    client = Luduvo(
        os.getenv("LUDUVO_TOKEN")
    )

For OAuth applications, keep the following values private:

- API tokens
- OAuth client secrets
- OAuth access tokens
- PKCE code verifiers

The authorization `state` value should also be verified before accepting an OAuth callback.

# Environment Variables

A common setup is:

    LUDUVO_TOKEN=your_token_here

Python:

    import os

    from luduvo import Luduvo

    client = Luduvo(
        os.getenv("LUDUVO_TOKEN")
    )

On Windows PowerShell:

    $env:LUDUVO_TOKEN="YOUR_TOKEN_HERE"

Then run:

    python main.py

# Package Structure

The project structure is:

    luduvo.py/
    ├── luduvo/
    │   ├── __init__.py
    │   └── client.py
    ├── demo/
    │   └── test.py
    ├── pyproject.toml
    └── README.md

The package is imported as:

    import luduvo

or:

    from luduvo import Luduvo

# API Client

The main client class is:

    from luduvo import Luduvo

    client = Luduvo()

The client exposes OAuth functionality through:

    client.oauth

Available OAuth methods:

    client.oauth.create(...)
    client.oauth.create_oauth(...)
    client.oauth.delete(...)
    client.oauth.list(...)
    client.oauth.create_pkce(...)
    client.oauth.authorize_url(...)
    client.oauth.token(...)
    client.oauth.userinfo(...)
