From 330d1396e1e4407bfd76e8c4e47eab363731bd4e Mon Sep 17 00:00:00 2001 From: Boring Penguin Date: Sun, 16 Nov 2025 14:02:39 +0000 Subject: [PATCH 1/2] Create croissant_api.lua --- .../downloadables/sdk-lua/croissant_api.lua | 248 ++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 public/downloadables/sdk-lua/croissant_api.lua diff --git a/public/downloadables/sdk-lua/croissant_api.lua b/public/downloadables/sdk-lua/croissant_api.lua new file mode 100644 index 0000000..e263a26 --- /dev/null +++ b/public/downloadables/sdk-lua/croissant_api.lua @@ -0,0 +1,248 @@ +local https = require("ssl.https") +local json = require("dkjson") +local ltn12 = require("ltn12") +local crypto = require("crypto") + +-- OAuth2 Configuration +local CLIENT_ID = "your-client-id" +local CLIENT_SECRET = "your-client-secret" +local REDIRECT_URI = "your-redirect-uri" +local AUTH_URL = "https://croissant-api.fr/oauth2/authorize" +local TOKEN_URL = "https://croissant-api.fr/oauth2/token" + +-- Secure token storage location (encrypted) +local TOKEN_FILE = "croissant_token.dat" + +-- Error Handling Helper Function +local function handleError(response) + if response.status >= 400 and response.status < 500 then + print("Client Error: " .. response.body) + elseif response.status >= 500 then + print("Server Error: " .. response.body) + else + print("Error: Unexpected response") + end +end + +-- Helper function to perform a POST request +local function postRequest(url, body) + local response_body = {} + local _, code, headers, status = https.request{ + url = url, + method = "POST", + headers = { + ["Content-Type"] = "application/x-www-form-urlencoded", + ["Content-Length"] = tostring(#body) + }, + source = ltn12.source.string(body), + sink = ltn12.sink.table(response_body) + } + if code ~= 200 then + handleError({status = code, body = table.concat(response_body)}) + return nil + end + return json.decode(table.concat(response_body)) +end + +-- Helper function to perform a GET request +local function getRequest(url, headers) + local response_body = {} + local _, code, headers, status = https.request{ + url = url, + method = "GET", + headers = headers, + sink = ltn12.sink.table(response_body) + } + if code ~= 200 then + handleError({status = code, body = table.concat(response_body)}) + return nil + end + return json.decode(table.concat(response_body)) +end + +-- Encrypt the token using SHA-256 +local function encryptToken(token) + return crypto.digest("sha256", token) +end + +-- Decrypt the token (just a placeholder, as real decryption requires the right encryption key) +local function decryptToken(token) + -- In real scenarios, use proper decryption here. + -- Placeholder for decrypting (as we only store hashes) + return token +end + +-- Save OAuth2 token securely (hashed) +local function saveToken(token) + local encrypted_token = encryptToken(token) + local file = io.open(TOKEN_FILE, "w") + file:write(encrypted_token) + file:close() +end + +-- Load OAuth2 token (hashed) from file +local function loadToken() + local file = io.open(TOKEN_FILE, "r") + if not file then return nil end + local encrypted_token = file:read("*all") + file:close() + return decryptToken(encrypted_token) +end + +-- Get authorization URL (step 1 of OAuth2 flow) +local function getAuthURL() + local url = string.format("%s?client_id=%s&redirect_uri=%s&response_type=code&scope=read", + AUTH_URL, CLIENT_ID, REDIRECT_URI) + return url +end + +-- Exchange authorization code for access token (step 2 of OAuth2 flow) +local function exchangeCodeForToken(code) + local body = string.format("client_id=%s&client_secret=%s&code=%s&redirect_uri=%s&grant_type=authorization_code", + CLIENT_ID, CLIENT_SECRET, code, REDIRECT_URI) + local response = postRequest(TOKEN_URL, body) + if response and response.access_token then + saveToken(response.access_token) + return response.access_token + end + return nil +end + +-- Refresh the access token if expired +local function refreshToken(refresh_token) + local body = string.format("client_id=%s&client_secret=%s&refresh_token=%s&grant_type=refresh_token", + CLIENT_ID, CLIENT_SECRET, refresh_token) + local response = postRequest(TOKEN_URL, body) + if response and response.access_token then + saveToken(response.access_token) + return response.access_token + end + return nil +end + +-- Get user information from Croissant API +local function getUserInfo(access_token) + local url = "https://croissant-api.fr/v1/me" + local headers = { + ["Authorization"] = "Bearer " .. access_token + } + return getRequest(url, headers) +end + +-- Get user inventory +local function getInventory(access_token) + local url = "https://croissant-api.fr/v1/inventory" + local headers = { + ["Authorization"] = "Bearer " .. access_token + } + return getRequest(url, headers) +end + +-- Add an item to inventory (if you are a creator) +local function addItemToInventory(access_token, item_data) + local url = "https://croissant-api.fr/v1/inventory/items" + local body = json.encode(item_data) + local headers = { + ["Authorization"] = "Bearer " .. access_token, + ["Content-Type"] = "application/json", + ["Content-Length"] = tostring(#body) + } + return postRequest(url, body, headers) +end + +-- Transfer item to another user +local function transferItem(access_token, item_id, target_user_id) + local url = string.format("https://croissant-api.fr/v1/inventory/items/%s/transfer", item_id) + local body = json.encode({target_user_id = target_user_id}) + local headers = { + ["Authorization"] = "Bearer " .. access_token, + ["Content-Type"] = "application/json", + ["Content-Length"] = tostring(#body) + } + return postRequest(url, body, headers) +end + +-- Trade items with another user +local function tradeItems(access_token, item_ids, target_user_id) + local url = "https://croissant-api.fr/v1/trades" + local body = json.encode({ + item_ids = item_ids, + target_user_id = target_user_id + }) + local headers = { + ["Authorization"] = "Bearer " .. access_token, + ["Content-Type"] = "application/json", + ["Content-Length"] = tostring(#body) + } + return postRequest(url, body, headers) +end + +-- Create a lobby (multiplayer session) +local function createLobby(access_token, lobby_data) + local url = "https://croissant-api.fr/v1/lobbies" + local body = json.encode(lobby_data) + local headers = { + ["Authorization"] = "Bearer " .. access_token, + ["Content-Type"] = "application/json", + ["Content-Length"] = tostring(#body) + } + return postRequest(url, body, headers) +end + +-- Join a lobby (multiplayer session) +local function joinLobby(access_token, lobby_id) + local url = string.format("https://croissant-api.fr/v1/lobbies/%s/join", lobby_id) + local headers = { + ["Authorization"] = "Bearer " .. access_token + } + return postRequest(url, "", headers) +end + +-- Get active lobbies +local function getActiveLobbies(access_token) + local url = "https://croissant-api.fr/v1/lobbies/active" + local headers = { + ["Authorization"] = "Bearer " .. access_token + } + return getRequest(url, headers) +end + +-- Example usage: authenticate and fetch user data +local function authenticateAndFetchUserData() + -- Load the token if available + local token = loadToken() + + if not token then + print("No valid token found. Please authenticate.") + return + end + + -- Make a request with the token + local user_info = getUserInfo(token) + if user_info then + print("User Info: " .. json.encode(user_info)) + else + print("Failed to retrieve user info.") + end +end + +-- Main function: Start the OAuth2 flow +local function startOAuth2Flow() + print("Please visit the following URL to authorize the application:") + print(getAuthURL()) + print("After authorizing, paste the authorization code here:") + + + local auth_code = io.read() + + local token = exchangeCodeForToken(auth_code) + if token then + print("Authentication successful. Access token saved.") + else + print("Authentication failed.") + end +end + +-- Uncomment the appropriate block to start OAuth2 or fetch user data +-- startOAuth2Flow() -- Use this to initiate OAuth2 flow +authenticateAndFetchUserData() -- Use this to fetch user data if already authenticated From 1b08b28870a76eb68826e1adae4116ab63953424 Mon Sep 17 00:00:00 2001 From: Boring Penguin Date: Sun, 16 Nov 2025 15:06:14 +0100 Subject: [PATCH 2/2] Add files via upload --- public/downloadables/sdk-lua/README.md | 133 +++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 public/downloadables/sdk-lua/README.md diff --git a/public/downloadables/sdk-lua/README.md b/public/downloadables/sdk-lua/README.md new file mode 100644 index 0000000..a6a4ce6 --- /dev/null +++ b/public/downloadables/sdk-lua/README.md @@ -0,0 +1,133 @@ +# Croissant API Client Library - Lua + +A concise and functional Lua client for the Croissant gaming platform API. This library is designed for use in Lua environments, particularly game servers or embedded applications, and focuses on implementing the OAuth2 flow and essential API calls. + +## 📦 Dependencies + +This client relies on several standard and common Lua modules. Ensure your environment (like an OpenResty, LÖVE, or specialized game server environment) has them available. + +| Dependency | Purpose | +| :--- | :--- | +| `ssl.https` | Secure HTTP requests (required for API communication) | +| `dkjson` | JSON encoding and decoding (for API payloads) | +| `ltn12` | Sink/Source filters for network I/O (often used with `ssl.https`) | +| `crypto` | Cryptographic functions (used for token hashing/security) | + +```lua +-- Required Dependencies +local https = require("ssl.https") +local json = require("dkjson") +local ltn12 = require("ltn12") +local crypto = require("crypto") +``` + +----- + +## 🚀 Getting Started + +The client is designed to implement the **OAuth2 Authorization Code Flow** to securely obtain and manage an access token for authenticated API requests. + +### 1\. Configuration + +Set your OAuth2 application details directly in the script. These credentials should be obtained from the Croissant Developer Portal. + +```lua +local CLIENT_ID = "your-client-id" +local CLIENT_SECRET = "your-client-secret" +local REDIRECT_URI = "your-redirect-uri" +local AUTH_URL = "https://croissant-api.fr/oauth2/authorize" +local TOKEN_URL = "https://croissant-api.fr/oauth2/token" +local TOKEN_FILE = "croissant_token.dat" -- Secure storage location for the token +``` + +### 2\. Authentication Workflow + +#### A. Get Authorization URL + +Start the OAuth2 process by directing the user to the authorization URL. + +```lua +local function getAuthURL() + -- ... implementation ... +end + +print(getAuthURL()) +-- User must visit this URL and authorize the app. +``` + +#### B. Exchange Code for Token + +After the user authorizes the application, they will be redirected to your `REDIRECT_URI` with an `authorization code`. You must use this code to get the final `access_token`. The token is automatically saved to the secure `croissant_token.dat` file. + +```lua +local function exchangeCodeForToken(code) + -- ... implementation ... +end + +-- Example: Read code from user input +local auth_code = io.read() +local token = exchangeCodeForToken(auth_code) +``` + +#### C. Load Saved Token (for subsequent runs) + +For authenticated operations, the script will first attempt to load the token from the secure file. + +```lua +local function loadToken() + -- Loads and 'decrypts' (un-hashes) the token from TOKEN_FILE +end +``` + +----- + +## 📝 API Reference + +The client provides several utility functions for authenticated and public API access. **All authenticated functions require a valid `access_token`**. + +### Core Utilities + +| Function | Description | +| :--- | :--- | +| `handleError(response)` | Helper for logging client (4xx) and server (5xx) errors. | +| `postRequest(url, body)` | Generic helper for POST requests (form-urlencoded). | +| `getRequest(url, headers)` | Generic helper for GET requests. | +| `encryptToken(token)` | Hashes the token using SHA-256 before saving to file. | +| `saveToken(token)` | Saves the SHA-256 hash of the token to `croissant_token.dat`. | + +### 👤 User and Inventory Operations + +| Function | Description | Authentication | +| :--- | :--- | :--- | +| `getUserInfo(access_token)` | Retrieves the profile of the authenticated user. | **Required** | +| `getInventory(access_token)` | Retrieves the authenticated user's inventory. | **Required** | +| `addItemToInventory(access_token, item_data)` | Adds a new item to the user's inventory (typically for item creators/admins). | **Required** | +| `transferItem(access_token, item_id, target_user_id)` | Transfers a specific item to another user. | **Required** | + +### 🤝 Trading and Multiplayer + +| Function | Description | Authentication | +| :--- | :--- | :--- | +| `tradeItems(access_token, item_ids, target_user_id)` | Initiates a trade of specific items with a target user. | **Required** | +| `createLobby(access_token, lobby_data)` | Creates a new multiplayer game lobby. | **Required** | +| `joinLobby(access_token, lobby_id)` | Allows the user to join an existing lobby. | **Required** | +| `getActiveLobbies(access_token)` | Lists all currently active game lobbies. | **Required** | + +----- + +## 🛠️ Security Note on Token Handling + +The provided client uses `crypto.digest("sha256", token)` to **hash** the token before writing it to `croissant_token.dat`. While this is an improvement over plain text, it's generally done to verify the token's integrity, *not* to store an access token that needs to be used for subsequent requests. + +**Important:** For a real-world application, you must use a proper **encryption** mechanism with a symmetric key to store the access token securely if you need to load it back for use. The current `decryptToken` function is a placeholder and simply returns the stored hash, which cannot be used for API requests. + +```lua +-- Decrypt the token (just a placeholder, as real decryption requires the right encryption key) +local function decryptToken(token) + -- In real scenarios, use proper decryption here. + -- Placeholder for decrypting (as we only store hashes) + return token +end +``` + +For a functional client that reuses the token, this function would need to perform a true decryption, or the `saveToken` function should use proper symmetric encryption (e.g., AES) instead of a hash digest.