Lukittu - License System icon

Lukittu - License System -----

Free & open-source Minecraft plugin license system



Notice: Usage on SpigotMC
You are prohibited from implementing any form of licensing system in the premium plugins you sell on SpigotMC.org. For more details, refer to the Premium Resource Guidelines on SpigotMC, specifically the section regarding DRM systems.

Lukittu - The Ultimate Open-Source Software Licensing Solution
Lukittu is a cutting-edge, open-source software licensing service designed to secure and track your applications. With powerful APIs, it adds an essential licensing layer to protect your proprietary software from unauthorized use and distribution. Lukittu is ideal for applications such as game scripts and add-ons on platforms like Minecraft, FiveM, and Roblox.

[​IMG]

Key Features That Set Lukittu Apart
  • Fully Automated Licensing Flows & Integrations: Easily integrate with popular platforms like BuiltByBit, Stripe, Discord and others for seamless licensing management.
  • Comprehensive Customer & Configuration Management: Enjoy full control with powerful tools for customer management and flexible configuration options.
  • Real-Time Analytics & Detailed Audit Logs: Track usage and monitor security in real-time with in-depth analytics and audit logs.
  • Collaboration Made Easy: Manage multiple teams with ease and streamline collaboration on your projects.
  • Keys-In-Hand, No Hosting Required: Take advantage of free managed hosting without the hassle of managing servers yourself.
  • Completely Free & Open-Source: Fully open-source, allowing you to contribute and improve the system.
  • Community-Driven Development: Features and improvements are shaped by the community, ensuring Lukittu evolves based on real user needs.
  • Industry-Leading Security: Secure your applications with advanced features like Java classloader and robust industry standards.
[​IMG]

Try Lukittu Now - Free Managed Hosting

Lukittu offers a free managed hosting solution that you can start using instantly. No server setup is required, making it perfect for those who need a seamless, hassle-free experience.

Click here to test live app!

[​IMG]

Why Lukittu?
Finding a reliable, open-source licensing system can be tough. I’ve been in this space since 2020, and the market has been saturated with limited options. Lukittu aims to fill this gap and become the go-to solution for software licensing.


This project is driven by the community—your feedback and contributions will directly influence its evolution. Lukittu is here to serve YOU, not to make a profit. All revenue goes toward covering hosting and operational costs.

[​IMG]

Hosting Options

Lukittu offers managed hosting for free, ensuring a smooth out-of-the-box experience. For those who prefer self-hosting, be aware that the system is designed for B2C SaaS, and self-hosting requires knowledge of Node.js and Docker.

Learn More About Hosting

[​IMG]

Getting Started - Java & Code Examples
Need help getting started? Check out our simple Java code examples to see how easy it is to integrate Lukittu into your projects.

[​IMG]

Useful Links
[​IMG]

Code examples

Main
Code (Java):
package org.lukittu.java_simple ;

import java.util.concurrent.Executors ;
import java.util.concurrent.ScheduledExecutorService ;
import java.util.concurrent.TimeUnit ;
import java.util.logging.Level ;

import org.bukkit.plugin.java.JavaPlugin ;

/**
* Main plugin class for the LukittuSimple plugin with Lukittu license
* verification.
* Handles plugin lifecycle, license checks, and periodic heartbeat requests.
*
* ⚠️ SECURITY WARNING FOR PRODUCTION USE ⚠️
*
* This implementation loads all license parameters from configuration for
* simplicity.
* In a production environment, only the customer license key should be
* configurable.
*
* All other Lukittu parameters (team ID, product ID, public key) should be
* hardcoded
* in your compiled JAR and protected with code obfuscation to prevent
* tampering.
*/

public final class Simple extends JavaPlugin {

    /**
     * Static instance of the plugin for global access from other classes.
     */

    public static Simple INSTANCE ;

    /**
     * Flag indicating whether the license validation was successful.
     * This is set by the LukittuLicenseVerify class after verification.
     */

    public boolean valid ;

    /**
     * Scheduled executor for periodic tasks.
     */

    private ScheduledExecutorService scheduler ;

    /**
     * Called when the plugin is enabled by the server.
     * Handles configuration loading, license verification and setup.
     */

    @Override
    public void onEnable ( ) {
        INSTANCE = this ;

        // Set up logging format
        setupLogging ( ) ;

        // Save default config if it doesn't exist
        saveDefaultConfig ( ) ;

        // Define a record to hold license configuration with proper types
        record LicenseConfig ( String key, String teamId, String productId, String publicKey ) {
        }

        // NOTE: In production, only load the license key from config
        // and hardcode other values for security
        var licenseConfig = new LicenseConfig (
                getConfig ( ). getString ( "license.key", "" ),
                getConfig ( ). getString ( "license.team-id", "" ), // Should be hardcoded in production
                getConfig ( ). getString ( "license.product-id", "" ), // Should be hardcoded in production
                getConfig ( ). getString ( "license.public-key", "" ) ) ; // Should be hardcoded in production

        // Check if all required license configuration values are provided
        if (licenseConfig. key ( ). isEmpty ( ) || licenseConfig. teamId ( ). isEmpty ( ) || licenseConfig. productId ( ). isEmpty ( ) ) {
            logMessage ( "License configuration missing. Check your config.yml!" ) ;
            getServer ( ). getPluginManager ( ). disablePlugin ( this ) ;
            return ;
        }

        // Verify the license key on startup
        getLogger ( ). info ( "Verifying Lukittu license..." ) ;
        try {
            LukittuLicenseVerify. verifyKey (
                    licenseConfig. key ( ),
                    licenseConfig. teamId ( ),
                    licenseConfig. productId ( ),
                    licenseConfig. publicKey ( ) ) ;

            // If license verification failed, disable the plugin
            if ( !valid ) {
                // Verification already showed the appropriate error message
                getServer ( ). getPluginManager ( ). disablePlugin ( this ) ;
                return ;
            }

            // Only setup heartbeat and enable plugin if license is valid
            setupHeartbeatScheduler (
                    licenseConfig. key ( ),
                    licenseConfig. teamId ( ),
                    licenseConfig. productId ( ) ) ;

            logMessage ( "Plugin enabled with valid license" ) ;
        } catch ( Exception e ) {
            getLogger ( ). severe ( "Unexpected error during license verification: " + e. getMessage ( ) ) ;
            logMessage ( "License verification failed due to an unexpected error" ) ;
            // Ensure plugin is always disabled on any exception
            getServer ( ). getPluginManager ( ). disablePlugin ( this ) ;
        }
    }

    /**
     * Sets up a scheduled task to send periodic heartbeat requests to the license
     * server.
     * This keeps the license active and validates it's still in use.
     *
     * @param licenseKey The license key to validate
     * @param teamId     The team ID for the license API
     * @param productId  The product ID for the license API
     */

    private void setupHeartbeatScheduler ( String licenseKey, String teamId, String productId ) {
        scheduler = Executors. newSingleThreadScheduledExecutor ( ) ;
        scheduler. scheduleAtFixedRate ( ( ) -> {
            try {
                LukittuLicenseVerify. sendHeartbeat (teamId, licenseKey, productId ) ;
                getLogger ( ). fine ( "Heartbeat sent successfully" ) ;
            } catch ( Exception e ) {
                // Heartbeat failures should be silent, just log at warning level
                getLogger ( ). log (Level. WARNING, "Failed to send heartbeat", e ) ;
            }
        }, 15, 15, TimeUnit. MINUTES ) ;
    }

    /**
     * Configure default logging level for the plugin
     */

    private void setupLogging ( ) {
        getLogger ( ). setLevel (Level. INFO ) ;
    }

    /**
     * Logs a license-related message with a specific prefix to make it easily
     * identifiable
     *
     * @param message The message to log
     */

    public void logMessage ( String message ) {
        getLogger ( ). info ( "LUKITTU LICENSE: " + message ) ;
    }

    /**
     * Called when the plugin is disabled by the server.
     * Handles graceful shutdown of background tasks.
     */

    @Override
    public void onDisable ( ) {
        // Shutdown the scheduler if it exists
        if (scheduler != null && !scheduler. isShutdown ( ) ) {
            scheduler. shutdown ( ) ;
            try {
                if ( !scheduler. awaitTermination ( 5, TimeUnit. SECONDS ) ) {
                    scheduler. shutdownNow ( ) ;
                }
            } catch ( InterruptedException e ) {
                scheduler. shutdownNow ( ) ;
                Thread. currentThread ( ). interrupt ( ) ;
            }
        }

        getLogger ( ). info ( "LukittuSimple plugin disabled!" ) ;
    }
}
Verify
Code (Java):
package org.lukittu.java_simple ;

import java.io.BufferedReader ;
import java.io.IOException ;
import java.io.InputStream ;
import java.io.InputStreamReader ;
import java.net.HttpURLConnection ;
import java.net.InetAddress ;
import java.net.URI ;
import java.nio.charset.StandardCharsets ;
import java.security.KeyFactory ;
import java.security.SecureRandom ;
import java.security.Signature ;
import java.security.spec.X509EncodedKeySpec ;
import java.util.Base64 ;
import java.util.Collections ;
import java.util.HashMap ;
import java.util.Map ;
import java.util.Optional ;
import java.util.UUID ;
import java.util.logging.Level ;

import com.google.gson.Gson ;
import com.google.gson.GsonBuilder ;
import com.google.gson.JsonObject ;

/**
* Handles license verification and validation for the Lukittu licensing system.
* Manages the communication with the license server, validates responses,
* and performs cryptographic verification of license challenges.
*
* ⚠️ SECURITY WARNING FOR PRODUCTION USE ⚠️
*
* THIS IS A DEMONSTRATION IMPLEMENTATION ONLY.
*
* In a production environment:
* 1. This entire class should be heavily obfuscated
* 2. All constants (API URLs, team ID, product ID, public key) should be
* hardcoded
* and encrypted/obfuscated rather than loaded from config
* 3. Only the license key itself should be configurable by end users
* 4. Add anti-tampering measures to detect modifications to your code
* 5. Consider using native code protection (JNI/JNIC) for critical sections
*
* Failure to properly protect this code may result in license bypass attempts.
*/

public class LukittuLicenseVerify {

    // Constants for API communication
    private static final String RESULT_KEY = "result" ;
    private static final String VALID_KEY = "valid" ;
    private static final String API_BASE_URL = "https://app.lukittu.com/api/v1/client/teams" ;
    private static final String VERIFY_ENDPOINT = "/verification/verify" ;
    private static final String HEARTBEAT_ENDPOINT = "/verification/heartbeat" ;
    private static final String VERSION = "1.0.0" ;
    private static final int TIMEOUT_MILLIS = 10000 ; // 10 seconds
    private static final String ERROR_CODE_KEY = "code" ;
    private static final String ERROR_DETAILS_KEY = "details" ;

    // Static variables
    /**
     * Unique identifier for this server/device installation
     */

    public static String DEVICE_IDENTIFIER ;

    /**
     * Map of error codes to user-friendly error messages
     */

    private static final Map < String, String > ERROR_MESSAGES ;

    /**
     * JSON parser/formatter for API communication
     */

    private static final Gson GSON = new GsonBuilder ( )
            . disableHtmlEscaping ( )
            . setPrettyPrinting ( )
            . create ( ) ;

    // Initialize static error messages
    static {
        Map < String, String > messages = new HashMap <> ( ) ;
        messages. put ( "RELEASE_NOT_FOUND", "Invalid version specified in config." ) ;
        messages. put ( "LICENSE_NOT_FOUND", "License not specified in config.yml, or it is invalid." ) ;
        messages. put ( "IP_LIMIT_REACHED",
                "License's IP address limit has been reached. Contact support if you have issues with this." ) ;
        messages. put ( "MAXIMUM_CONCURRENT_SEATS", "Maximum devices connected from the same license." ) ;
        messages. put ( "RATE_LIMIT",
                "Too many connections in a short time from the same IP address. Please wait a while!" ) ;
        messages. put ( "LICENSE_EXPIRED", "The license has expired." ) ;
        messages. put ( "INTERNAL_SERVER_ERROR", "Upstream service has issues. Please notify support!" ) ;
        messages. put ( "BAD_REQUEST", "Invalid request format or parameters. Check your license configuration." ) ;
        ERROR_MESSAGES = Collections. unmodifiableMap (messages ) ;
    }

    /**
     * Main license verification method that initiates the verification process.
     * Generates a challenge, sends it to the license server, and validates the
     * response.
     *
     * ⚠️ SECURITY NOTE: In production, only licenseKey should come from
     * configuration.
     * The teamId, productId, and publicKey should be hardcoded, obfuscated
     * constants.
     *
     * @param licenseKey The license key from config
     * @param teamId     The team ID from config (should be hardcoded in production)
     * @param productId  The product ID from config (should be hardcoded in
     *                   production)
     * @param publicKey  The public key used to verify the server's signature
     *                   (should be hardcoded in production)
     * @throws IOException If a network or server error occurs
     * @throws Exception   If validation fails for any reason
     */

    public static void verifyKey ( String licenseKey, String teamId, String productId, String publicKey )
            throws Exception {
        DEVICE_IDENTIFIER = getHardwareIdentifier ( ) ;

        // Generate a random challenge
        var challenge = generateRandomChallenge ( ) ;

        // Construct the URL for the API call with team ID
        var url = API_BASE_URL + "/" + teamId + VERIFY_ENDPOINT ;

        var jsonBody = String. format ( "" "
                {
                  "
licenseKey ": " %s ",
                  "
productId ": " %s ",
                  "
challenge ": " %s ",
                  "
version ": " %s ",
                  "
deviceIdentifier ": " %s "
                }"
"", licenseKey, productId, challenge, VERSION, DEVICE_IDENTIFIER ) ;

        boolean verificationSuccess = fetchAndHandleResponse (url, jsonBody, publicKey, challenge ) ;

        // Throw exception if verification failed to ensure it's caught in Simple's
        // onEnable
        if ( !verificationSuccess ) {
            throw new Exception ( "License verification failed" ) ;
        }
    }

    /**
     * Generates a random challenge string to prevent replay attacks.
     * The server will sign this challenge in its response.
     *
     * @return A secure random hex string to use as challenge
     */

    private static String generateRandomChallenge ( ) {
        var secureRandom = new SecureRandom ( ) ;
        var randomBytes = new byte [ 32 ] ;
        secureRandom. nextBytes (randomBytes ) ;
        return bytesToHex (randomBytes ) ;
    }

    /**
     * Utility method to convert byte arrays to hexadecimal strings.
     *
     * @param bytes The byte array to convert
     * @return A hex string representation of the bytes
     */

    public static String bytesToHex ( byte [ ] bytes ) {
        var result = new StringBuilder (bytes. length * 2 ) ;
        for ( byte b : bytes ) {
            result. append ( String. format ( "%02x", b ) ) ;
        }
        return result. toString ( ) ;
    }

    /**
     * Makes the HTTP request to the license server and processes the response.
     *
     * @param urlString       The full URL of the license API endpoint
     * @param jsonBody        The request body to send
     * @param publicKeyBase64 The public key to verify the response
     * @param challenge       The challenge string that should be signed in the
     *                        response
     * @return true if verification succeeded, false if it failed
     * @throws IOException If a network error occurs
     */

    public static boolean fetchAndHandleResponse ( String urlString, String jsonBody, String publicKeyBase64,
            String challenge ) throws IOException {
        HttpURLConnection connection = null ;
        boolean success = false ;

        try {
            var url = URI. create (urlString ). toURL ( ) ;
            connection = ( HttpURLConnection ) url. openConnection ( ) ;
            connection. setRequestMethod ( "POST" ) ;
            connection. setRequestProperty ( "Content-Type", "application/json" ) ;
            connection. setRequestProperty ( "User-Agent", buildUserAgent ( ) ) ;
            connection. setConnectTimeout (TIMEOUT_MILLIS ) ;
            connection. setReadTimeout (TIMEOUT_MILLIS ) ;
            connection. setDoOutput ( true ) ;

            try (var os = connection. getOutputStream ( ) ) {
                var input = jsonBody. getBytes (StandardCharsets. UTF_8 ) ;
                os. write (input, 0, input. length ) ;
            }

            int responseCode = connection. getResponseCode ( ) ;

            if (responseCode == HttpURLConnection. HTTP_OK ) {
                try (var inputStream = connection. getInputStream ( ) ) {
                    success = handleJsonResponse (inputStream, publicKeyBase64, challenge ) ;
                }
            } else {
                try (var errorStream = connection. getErrorStream ( ) ) {
                    // Try to extract error details from the error response
                    if (errorStream != null ) {
                        handleJsonResponse (errorStream, null, null ) ;
                    }
                }
                // Show HTTP error if in 4xx or 5xx range
                if (responseCode >= 400 ) {
                    Simple. INSTANCE. logMessage ( "HTTP Error: " + responseCode +
                            " - Check your team ID, product ID and license key" ) ;
                }
            }
        } catch ( Exception e ) {
            Simple. INSTANCE. getLogger ( ). log (Level. SEVERE, "Connection to Lukittu service failed", e ) ;
            Simple. INSTANCE. logMessage ( "Connection failure! Check server connectivity" ) ;
            try {
                if (connection != null && connection. getErrorStream ( ) != null ) {
                    handleJsonResponse (connection. getErrorStream ( ), null, null ) ;
                }
            } catch ( IOException e1 ) {
                Simple. INSTANCE. getLogger ( ). log (Level. SEVERE, "Failed to parse error response", e1 ) ;
            }
            throw new IOException ( "Connection to license server failed", e ) ;
        } finally {
            if (connection != null ) {
                connection. disconnect ( ) ;
            }
        }

        return success ;
    }

    /**
     * Parses and validates the JSON response from the license server.
     * If verification succeeds, it sets the valid state in the main plugin.
     *
     * @param inputStream The input stream containing the JSON response
     * @param publicKey   The public key for signature verification
     * @param challenge   The original challenge to verify
     * @throws IOException If there's an error reading the response
     * @return true if validation was successful, false if errors were encountered
     */

    private static boolean handleJsonResponse ( InputStream inputStream, String publicKey, String challenge )
            throws IOException {
        if (inputStream == null ) {
            throw new IOException ( "Input stream is null" ) ;
        }

        try (var reader = new BufferedReader ( new InputStreamReader (inputStream, StandardCharsets. UTF_8 ) ) ) {
            var json = GSON. fromJson (reader, JsonObject. class ) ;

            // Log the full JSON response for debugging
            var respString = GSON. toJson (json ) ;
            logResponse (respString ) ;

            // Check if this is a success response that needs validation
            if (publicKey != null && challenge != null ) {
                if (validateResponse (json ) && validateChallenge (json, challenge, publicKey ) ) {
                    setValidState ( ) ;
                    return true ;
                }
            }

            // At this point, there's an error we need to handle
            if (json. has (RESULT_KEY ) ) {
                var result = json. getAsJsonObject (RESULT_KEY ) ;

                // Extract error details if available
                if (result. has (ERROR_CODE_KEY ) ) {
                    String errorCode = result. get (ERROR_CODE_KEY ). getAsString ( ) ;
                    String errorMessage = ERROR_MESSAGES. getOrDefault (errorCode,
                            "Lukittu license check failed with code: " + errorCode ) ;

                    // Add details if available
                    if (result. has (ERROR_DETAILS_KEY ) ) {
                        errorMessage += " (" + result. get (ERROR_DETAILS_KEY ). getAsString ( ) + ")" ;
                    }

                    Simple. INSTANCE. logMessage ( "Error: " + errorMessage ) ;
                    return false ;
                }
            }

            // If we didn't find specific error info, use the general handling
            return !handleErrorCodes (respString ) ;
        }
    }

    /**
     * Validates the digital signature of the challenge in the server response.
     * This proves the response came from the legitimate license server.
     *
     * @param response          The JSON response from the server
     * @param originalChallenge The original challenge we sent
     * @param base64PublicKey   The public key to verify the signature
     * @return true if the signature is valid, false otherwise
     */

    public static boolean validateChallenge (JsonObject response, String originalChallenge, String base64PublicKey ) {
        try {
            if ( !validateResponse (response ) || originalChallenge == null || base64PublicKey == null ) {
                return false ;
            }

            var signedChallenge = response. getAsJsonObject (RESULT_KEY )
                    . get ( "challengeResponse" ). getAsString ( ) ;

            return verifySignature (originalChallenge, signedChallenge, base64PublicKey ) ;
        } catch ( Exception e ) {
            Simple. INSTANCE. getLogger ( ). log (Level. SEVERE, "Challenge validation failed", e ) ;
            Simple. INSTANCE. logMessage ( "Signature verification failed! Possible tampering detected" ) ;
            return false ;
        }
    }

    /**
     * Performs the actual cryptographic signature verification.
     * Uses RSA with SHA256 to verify that the challenge was signed by the license
     * server.
     *
     * @param challenge       The original challenge string
     * @param signatureHex    The hex-encoded signature to verify
     * @param base64PublicKey The base64-encoded public key
     * @return true if the signature is valid, false otherwise
     */

    public static boolean verifySignature ( String challenge, String signatureHex, String base64PublicKey ) {
        try {
            var signatureBytes = hexStringToByteArray (signatureHex ) ;
            var decodedKeyBytes = Base64. getDecoder ( ). decode (base64PublicKey ) ;

            var decodedKeyString = new String (decodedKeyBytes )
                    . replace ( "-----BEGIN PUBLIC KEY-----", "" )
                    . replace ( "-----END PUBLIC KEY-----", "" )
                    . replaceAll ( "\\s", "" ) ;

            var publicKeyBytes = Base64. getDecoder ( ). decode (decodedKeyString ) ;
            var keySpec = new X509EncodedKeySpec (publicKeyBytes ) ;
            var keyFactory = KeyFactory. getInstance ( "RSA" ) ;
            var publicKey = keyFactory. generatePublic (keySpec ) ;

            var signature = Signature. getInstance ( "SHA256withRSA" ) ;
            signature. initVerify (publicKey ) ;
            signature. update (challenge. getBytes ( ) ) ;

            return signature. verify (signatureBytes ) ;
        } catch ( IllegalArgumentException e ) {
            Simple. INSTANCE. getLogger ( ). log (Level. SEVERE, "Invalid Base64 input for public key", e ) ;
            Simple. INSTANCE. logMessage ( "Invalid public key format! Contact support" ) ;
            return false ;
        } catch ( Exception e ) {
            Simple. INSTANCE. getLogger ( ). log (Level. SEVERE, "Signature verification failed", e ) ;
            return false ;
        }
    }

    /**
     * Utility method to convert a hex string to a byte array.
     *
     * @param hex The hex string to convert
     * @return The resulting byte array
     */

    private static byte [ ] hexStringToByteArray ( String hex ) {
        var len = hex. length ( ) ;
        var data = new byte [len / 2 ] ;
        for ( int i = 0 ; i < len ; i += 2 ) {
            data [i / 2 ] = ( byte ) ( ( Character. digit (hex. charAt (i ), 16 ) << 4 )
                    + Character. digit (hex. charAt (i + 1 ), 16 ) ) ;
        }
        return data ;
    }

    /**
     * Checks if the license server response indicates a valid license.
     *
     * @param json The JSON response object to validate
     * @return true if the license is valid, false otherwise
     */

    private static boolean validateResponse (JsonObject json ) {
        try {
            var result = json. getAsJsonObject (RESULT_KEY ) ;
            return result != null &&
                    result. has (VALID_KEY ) &&
                    result. get (VALID_KEY ). getAsBoolean ( ) ;
        } catch ( Exception e ) {
            return false ;
        }
    }

    /**
     * Updates the main plugin to indicate the license is valid.
     * Uses reflection to avoid direct dependencies.
     */

    private static void setValidState ( ) {
        try {
            var validField = Simple. class. getDeclaredField ( "valid" ) ;
            validField. setAccessible ( true ) ;
            validField. set (Simple. INSTANCE, true ) ;
        } catch ( Exception e ) {
            Simple. INSTANCE. getLogger ( ). log (Level. WARNING, "Failed to set valid state", e ) ;
        }
    }

    /**
     * Builds a consistent User-Agent string for API requests.
     * Helps with tracking and debugging on the server side.
     *
     * @return The formatted User-Agent string
     */

    private static String buildUserAgent ( ) {
        return String. format ( "LukittuLoader/%s (%s %s; %s)",
                VERSION,
                System. getProperty ( "os.name" ),
                System. getProperty ( "os.version" ),
                System. getProperty ( "os.arch" ) ) ;
    }

    /**
     * Logs API responses for debugging purposes.
     * Useful for diagnosing license problems.
     *
     * @param response The JSON response string
     */

    private static void logResponse ( String response ) {
        if (response != null ) {
            Simple. INSTANCE. getLogger ( ). info ( "Received JSON response (pretty printed):" ) ;
            Simple. INSTANCE. getLogger ( ). info (response ) ;
        }
    }

    /**
     * Checks API responses for known error codes and provides user-friendly
     * messages.
     *
     * @param response The response string to check
     * @return true if an error was found and handled, false otherwise
     */

    private static boolean handleErrorCodes ( final String response ) {
        if (response == null ) {
            return false ;
        }

        // Find specific error in the response
        var errorEntry = findErrorInResponse (response ) ;

        // Handle error if found
        if (errorEntry. isPresent ( ) ) {
            var errorMessage = errorEntry. get ( ). getValue ( ) ;
            Simple. INSTANCE. getLogger ( ). severe (errorMessage ) ;
            Simple. INSTANCE. logMessage ( "Error: " + errorMessage ) ;
            return true ;
        }

        // Generic error for any validation failure not otherwise caught
        if (response. contains ( "\"valid\":false" ) ) {
            Simple. INSTANCE. logMessage ( "License validation failed. Check your license configuration" ) ;
            return true ;
        }

        // No error found
        return false ;
    }

    /**
     * Finds the first matching error code in the response.
     *
     * @param response The API response to search
     * @return An Optional containing the matching error entry, or empty if none
     *         found
     */

    private static Optional < Map. Entry < String, String >> findErrorInResponse ( String response ) {
        return ERROR_MESSAGES. entrySet ( ). stream ( )
                . filter (entry -> response. contains (entry. getKey ( ) ) )
                . findFirst ( ) ;
    }

    /**
     * Sends a periodic heartbeat to the license server.
     * This keeps the license active and helps detect license violations.
     *
     * @param teamId     The team ID associated with the license
     * @param licenseKey The license key to validate
     * @param productId  The product ID associated with the license
     * @throws Exception If there's an error sending the heartbeat
     */

    public static void sendHeartbeat ( String teamId, String licenseKey, String productId ) throws Exception {
        var urlString = API_BASE_URL + "/" + teamId + HEARTBEAT_ENDPOINT ;
        var url = URI. create (urlString ). toURL ( ) ;

        var connection = ( HttpURLConnection ) url. openConnection ( ) ;
        connection. setRequestMethod ( "POST" ) ;
        connection. setRequestProperty ( "Content-Type", "application/json" ) ;
        connection. setRequestProperty ( "User-Agent", buildUserAgent ( ) ) ;
        connection. setConnectTimeout (TIMEOUT_MILLIS ) ;
        connection. setReadTimeout (TIMEOUT_MILLIS ) ;
        connection. setDoOutput ( true ) ;

        var jsonBody = String. format ( "" "
                {
                    "
licenseKey ": " %s ",
                    "
productId ": " %s ",
                    "
deviceIdentifier ": " %s "
                }"
"", licenseKey, productId, DEVICE_IDENTIFIER ) ;

        try (var os = connection. getOutputStream ( ) ) {
            var input = jsonBody. getBytes (StandardCharsets. UTF_8 ) ;
            os. write (input, 0, input. length ) ;
        }

        int responseCode = connection. getResponseCode ( ) ;

        // Read the response for logging purposes, even if we don't use it
        try (var is = (responseCode < HttpURLConnection. HTTP_BAD_REQUEST )
                ? connection. getInputStream ( )
                : connection. getErrorStream ( ) ;
                var br = new BufferedReader ( new InputStreamReader (is ) ) ) {

            var response = new StringBuilder ( ) ;
            String line ;
            while ( (line = br. readLine ( ) ) != null ) {
                response. append (line ) ;
            }

            if (responseCode >= HttpURLConnection. HTTP_BAD_REQUEST ) {
                Simple. INSTANCE. getLogger ( ). warning ( "Heartbeat failed with response code: " + responseCode ) ;
                handleErrorCodes (response. toString ( ) ) ;
            }
        } catch ( IOException e ) {
            Simple. INSTANCE. getLogger ( ). log (Level. WARNING, "Failed to read heartbeat response", e ) ;
        } finally {
            connection. disconnect ( ) ;
        }
    }

    /**
     * Generates a unique identifier for this server/device based on system
     * properties.
     * Used to limit the number of installations per license.
     *
     * Note: On virtual machines or containers, this may change between restarts.
     *
     * @return A UUID based on system properties or a random UUID if hardware info
     *         can't be retrieved
     */

    public static String getHardwareIdentifier ( ) {
        try {
            // Use standard approach instead of StructuredTaskScope (preview API)
            var osName = System. getProperty ( "os.name" ) ;
            var osVersion = System. getProperty ( "os.version" ) ;
            var osArch = System. getProperty ( "os.arch" ) ;
            var hostname = InetAddress. getLocalHost ( ). getHostName ( ) ;

            var combinedIdentifier = osName + osVersion + osArch + hostname ;
            return UUID. nameUUIDFromBytes (combinedIdentifier. getBytes ( ) ). toString ( ) ;
        } catch ( Exception e ) {
            Simple. INSTANCE. getLogger ( ). warning ( "Failed to get hardware identifier: " + e. getMessage ( ) ) ;
            Simple. INSTANCE. logMessage ( "Hostname retrieval failed, using random identifier" ) ;
            return UUID. randomUUID ( ). toString ( ) ;
        }
    }
}

[​IMG]

Support Lukittu!
Love what you see? Support the project by starring it on GitHub! It’s a free and easy way to show your support and help Lukittu grow. You can also earn exclusive Stargazer and Contributor roles on our Discord server.

Give Lukittu a Star on GitHub!

[​IMG]
Resource Information
Author:
----------
Total Downloads: 127
First Release: Mar 14, 2025
Last Update: Jun 24, 2025
Category: ---------------
All-Time Rating:
0 ratings
Find more info at lukittu.com...
Version -----
Released: --------------------
Downloads: ------
Version Rating:
----------------------
-- ratings