Update License Validator For ESP32 JWT Format

by SLV Team 46 views
Updating Main Application License Validator for ESP32 JWT-Like Format

Hey guys! Today, we're diving deep into how to update the main application license validator to support the ESP32 deployment tool’s JWT-like format. This is a crucial update that ensures our application can seamlessly validate licenses generated by the ESP32 tool. Let's break it down step by step.

Understanding the Task: License Format Compatibility Implementation

The Core Issue

At the heart of this task is a compatibility issue. The ESP32 deployment tool generates licenses in a JWT-like format, specifically base64url(payload).base64url(signature). Our main application, on the other hand, expects licenses in a PEM-wrapped AES-encrypted format, recognizable by the -----BEGIN SMC LICENSE----- header. This format mismatch prevents the main application from validating licenses generated by the ESP32 tool, which is a major roadblock we need to address.

Why This Matters

This isn't just a minor inconvenience; it's a high-priority issue because it directly impacts core functionality. If our application can't validate licenses, users won't be able to use the features they've paid for. Think of it like trying to use a key that doesn't fit the lock – frustrating and ultimately useless. So, we need to ensure that our application can understand and validate both license formats.

The Goal

Our goal is to update the main application license validator (/main/license/license-validator.ts) to support both the ESP32 tool's JWT-like format and the existing PEM AES-256-GCM encrypted format. This means our application needs to be versatile enough to handle either format seamlessly. We're essentially teaching our application to speak two different languages.

Implementation Requirements: Cracking the Code

1. Adding JWT-Like License Parsing

First things first, we need to teach our application how to understand the JWT-like format. This involves a few key steps:

  • parseJWTLicense() Method: We'll add a new method, parseJWTLicense(), to handle the ESP32 tool's format. This method will be responsible for taking the JWT-like token and breaking it down into its constituent parts.
  • base64urlToBuffer() Helper: JWTs use a slightly different encoding scheme called base64url. We'll need a helper function, base64urlToBuffer(), to properly decode the base64url-encoded parts of the token. Think of it as translating from one dialect of a language to another.
  • Signature Verification: A crucial part of JWT validation is verifying the signature. This ensures that the token hasn't been tampered with. We'll add signature verification capability, but we'll also allow for a bypass in development mode. This is like having a security checkpoint that can be temporarily disabled for testing purposes.

2. Updating the Main Parser

Next, we need to update our main parsing logic to handle both formats. This means modifying our existing parsePEMLicense() method to be more flexible:

  • Trying Both Formats: We'll modify parsePEMLicense() to attempt parsing the license as both a PEM format and a JWT-like format. It's like having a universal translator that can understand multiple languages.
  • Backward Compatibility: It’s crucial that we maintain backward compatibility with the existing PEM format. We don't want to break existing licenses! This is like making sure our new translator still speaks the old language.
  • Graceful Fallback: If one format fails, the parser should gracefully fall back to the other format. This is like having a backup plan in case the first attempt at translation fails.

3. Data Mapping: Bridging the Gap

JWTs have a specific structure, and our application expects data in a particular format (the LicenseData interface). We need to map the data from the ESP32 payload to our application's structure:

  • Field Naming Differences: JWTs might use different names for fields (e.g., customer vs customerName/organization). We need to handle these differences and ensure the data is mapped correctly.
  • Preserving Required Fields: All required fields in our LicenseData interface must be populated from the JWT payload. We can't afford to lose any critical information.

4. Validation & Testing: Ensuring Quality

Validation is key! We need to ensure that both the existing PEM format and the new JWT format are properly validated:

  • Existing Validation: All existing validation checks must continue to work for the PEM format. We can't compromise our existing security measures.
  • New JWT Validation: The JWT format must also pass all existing validation checks. It's like ensuring both languages meet the same standards of grammar and syntax.
  • MAC Address and Date Validation: Specific validations like MAC address format and date validation must work correctly for both formats. These are critical checks that ensure the license is valid and hasn't expired.

Technical Specifications: The Blueprint

ESP32 License Format

Let's take a closer look at the ESP32 license format we need to support:

Token: eyJtYWMiOiJkMDpjZjoxMzoxNjoyMToyOCIsImV4cGlyeSI6IjIxMjUtMTEtMDFUMTY6MzQ6NTYuMDc1WiIsImN1c3RvbWVyIjoiSDAwMTpIMDAxIiwiaXNzdWVkQXQiOiIyMDI1LTExLTAxVDE2OjM0OjU2LjA3NVoifQ.MEUCIQCE3KRc18-tp97K8eDes-ZVPkMwLjW0nVQixJL5OippZQIgLBYewk-_2EHHAEygcIQQm60mDR97-3OvDnJd6dx55Wc

Payload Structure:
{
  "mac": "d0:cf:13:16:22:28",
  "expiry": "2125-11-01T16:34:56.075Z",
  "customer": "H001:H001",
  "issuedAt": "2025-11-01T16:34:56.075Z"
}

This token consists of two parts separated by a dot: the payload and the signature. The payload contains information like the MAC address, expiry date, customer details, and issue date. The signature is used to verify the integrity of the token.

Target LicenseData Structure

Our application expects the license data in a specific format defined by the LicenseData interface:

interface LicenseData {
  customerName: string;    // From customer.split(':')[1]
  organization: string;    // From customer.split(':')[0]
  mac_address: string;     // Direct mapping
  expiry: string;         // Direct mapping
  issued_at: string;      // From issuedAt
  features: string[];     // Empty array default
}

We need to map the fields from the ESP32 payload to this structure, handling any necessary transformations (like splitting the customer field into organization and customerName).

Implementation Steps: The How-To Guide

Here’s a step-by-step guide to implementing the changes:

  1. Add Helper Methods:
    • base64urlToBuffer(str: string): Buffer: This function will decode a base64url-encoded string into a Buffer.
    • parseJWTLicense(token: string): LicenseData | null: This function will parse a JWT-like token and return a LicenseData object or null if parsing fails.
    • verifyLicenseSignature(payloadB64: string, signatureB64: string): boolean: This function will verify the signature of the JWT-like token. (We can consider a development mode bypass for this.)
  2. Refactor Existing Parser:
    • Move the current parsePEMLicense logic to parsePEMLicenseLegacy(). This keeps our code organized and makes it easier to manage.
    • Update parsePEMLicense() to try both formats (PEM and JWT-like).
  3. Add Data Mapping:
    • Convert the ESP32 payload to the LicenseData format.
    • Handle the customer field splitting (H001:H001 → org/customerName).
  4. Validation Compatibility:
    • Ensure all existing validation works for both formats.
    • Test MAC address validation.
    • Test date validation.

Files to Modify: Where the Magic Happens

  • Primary: /main/license/license-validator.ts – This is where most of the changes will occur.
  • Test: Create/update validation tests to ensure our changes are working correctly.
  • Related: /main/license/validator.ts (legacy) – Ensure no conflicts with the existing code.

Validation Requirements: Ensuring Quality and Security

We need to ensure that our changes are rock-solid. Here are the key validation requirements:

Build Validation (100% Pass Required)

  • npm run build – Must compile without errors. This ensures our code is syntactically correct.
  • npx tsc --noEmit – TypeScript validation 100% pass. This ensures our TypeScript types are correctly defined.

Code Quality (100% Pass Required)

  • npm run lint – ESLint validation 100% pass. This ensures our code adheres to our style guide and best practices.
  • All TypeScript types properly defined. This is crucial for maintainability and preventing runtime errors.
  • Error handling implemented. We need to handle potential errors gracefully and provide informative error messages.

Functional Testing: The Real Test

  • Existing PEM licenses still validate. We can't break existing functionality!
  • New JWT-like licenses validate successfully. This is the core goal of our task.
  • Invalid licenses are properly rejected. We need to ensure our application is secure and doesn't accept invalid licenses.
  • MAC address validation works for both formats.
  • Date validation works for both formats.

Success Criteria: How We Define Victory

  1. Backward Compatibility: Existing PEM licenses continue to work. This is non-negotiable.
  2. Forward Compatibility: New ESP32 JWT licenses validate successfully. This is the primary goal.
  3. Validation Integrity: All security validations maintained. We can't compromise security.
  4. Error Handling: Graceful failure modes with clear error messages. User experience is important.
  5. Code Quality: 100% pass on build, lint, and type validation. Clean code is maintainable code.

Testing Strategy: Our Battle Plan

Unit Tests

  • Test parseJWTLicense() with valid/invalid tokens. This ensures our JWT parsing logic is working correctly.
  • Test data mapping edge cases. This ensures we handle different scenarios and corner cases.
  • Test signature verification (if implemented). This verifies the integrity of the JWT tokens.

Integration Tests

  • Test complete validation flow with both license types. This tests the entire process from parsing to validation.
  • Test error scenarios and fallback behavior. This ensures our application handles errors gracefully.

Manual Tests

  • Validate with existing PEM license files. This confirms backward compatibility.
  • Validate with ESP32-generated JWT license files. This confirms forward compatibility.
  • Test UI activation flow with both formats. This tests the user-facing aspects of the changes.

Dependencies & Prerequisites: What We Need

  • No external dependencies required. We're using existing Node.js crypto modules.
  • Compatible with existing database schema. We don't need to make any database changes.
  • No changes to IPC handlers needed. This simplifies the task.

Risks & Mitigations: Planning for the Unexpected

  • Risk: Breaking existing PEM license validation.
    • Mitigation: Maintain separate legacy parser, thorough testing.
  • Risk: Security issues with JWT parsing.
    • Mitigation: Implement signature verification, validation checks.
  • Risk: Data mapping errors.
    • Mitigation: Comprehensive testing with various license formats.

Validation Checklist (Must Complete): Our Final Sanity Check

  • [ ] Build validation: 100% PASS
  • [ ] Linter validation: 100% PASS
  • [ ] TypeScript validation: 100% PASS
  • [ ] Existing PEM licenses work
  • [ ] New JWT licenses work
  • [ ] All validation checks work for both formats
  • [ ] Error handling tested
  • [ ] Integration tested with UI components

Conclusion

Updating the main application license validator to support the ESP32 JWT-like format is a critical task that requires careful planning and execution. By following these steps and paying close attention to validation, we can ensure a smooth transition and maintain the security and integrity of our application. Let's get to work and make this happen, guys! Remember, clear communication and collaboration are key to success. Happy coding! 🚀✨💻