UNPKG

@astermind/astermind-pro

Version:

Astermind Pro - Premium ML Toolkit with Advanced RAG, Reranking, Summarization, and Information Flow Analysis

374 lines 16.4 kB
/** * License management for Astermind Pro * Wraps @astermindai/license-runtime with convenience functions * Also propagates license to Astermind Synth for unified licensing * * SECURITY FEATURES: * - Cryptographic JWT signature validation using JWKS from license server * - Token format validation (JWT structure, required claims) * - Issuer validation (must match https://license.astermind.ai) * - Expiration checking * - Fake token rejection (invalid signatures are rejected even if payload looks valid) * - Multiple validation layers to prevent bypass attempts * * The license-runtime library validates tokens using ES256 signatures and public keys * fetched from the license server's JWKS endpoint. Fake tokens cannot bypass this validation. */ import { initLicenseRuntime, setLicenseToken, requireFeature, hasFeature, getLicenseState, base64urlDecodeJson } from '@astermindai/license-runtime'; let initialized = false; let currentToken = null; /** * Initialize the license runtime singleton for Astermind Pro. * Must be called before any other license functions. * Automatically loads token from ASTERMIND_LICENSE_TOKEN environment variable if present. * * This initializes the license runtime for: * - astermind-pro (primary) * - astermind-synth (propagated automatically) * * PROFESSIONAL LICENSING APPROACH: * - Always requires a valid license key (strict mode) * - Trial keys are obtained from the license server and have expiration dates * - No "eval mode" bypass - all usage requires a valid key * - For testing, use a test/dev key or mock the license runtime */ export function initializeLicense() { if (initialized) { return; } // Initialize for astermind-pro // Note: We accept both "astermind-pro" and "astermind-elm" tokens // The validation in setLicenseTokenFromString handles the audience flexibility initLicenseRuntime({ jwksUrl: 'https://license.astermind.ai/.well-known/astermind-license-keys.json', expectedIss: 'https://license.astermind.ai', expectedAud: 'astermind-pro', // Primary audience, but we'll accept astermind-elm in setLicenseTokenFromString mode: process.env.NODE_ENV === 'production' ? 'strict' : 'strict', // Always strict for Pro jwksMaxAgeSeconds: 300 }); initialized = true; // Auto-load from environment variable if present const envToken = typeof process !== 'undefined' && process.env?.ASTERMIND_LICENSE_TOKEN; if (envToken) { setLicenseTokenFromString(envToken).catch(err => { console.warn('[Astermind Pro] Failed to load license token from environment:', err); }); } } /** * Require a valid license (throws if not available). * Accepts both astermind-pro and astermind-elm licenses. * Use this before accessing premium features. * * SECURITY: This function performs multiple validation checks to prevent bypass attempts. * Do not modify or wrap this function in a way that suppresses errors. */ export function requireLicense() { if (!initialized) { initializeLicense(); } // Security: Verify license runtime is actually initialized const state = getLicenseState(); // First try astermind-pro try { requireFeature('astermind-pro'); // Additional security: Verify the state is actually valid if (state.status === 'valid' || hasFeature('astermind-pro')) { return; } } catch (err) { // Continue to check other options } // Try astermind-elm try { requireFeature('astermind-elm'); // Additional security: Verify the state is actually valid if (state.status === 'valid' || hasFeature('astermind-elm')) { return; } } catch (elmErr) { // Continue to check other options } // Try astermind-elm-basic try { requireFeature('astermind-elm-basic'); // Additional security: Verify the state is actually valid if (state.status === 'valid' || hasFeature('astermind-elm-basic')) { return; } } catch (basicErr) { // Continue to check other options } // Try astermind-elm-pro-full try { requireFeature('astermind-elm-pro-full'); // Additional security: Verify the state is actually valid if (state.status === 'valid' || hasFeature('astermind-elm-pro-full')) { return; } } catch (proFullErr) { // Continue to check token payload } // SECURITY: If all feature checks fail, but we have an astermind-elm token, // only accept it if it passed cryptographic validation (checked by license-runtime) // We verify the license state to ensure the token was actually validated if (currentToken) { const payload = parseTokenPayload(currentToken); if (payload?.aud === 'astermind-elm') { const features = payload?.features || []; // Accept any premium ELM features (e.g., "astermind-elm-premium-full", "astermind-elm-pro-full") const hasPremiumElmFeature = features.some((f) => f.includes('astermind-elm-premium') || f.includes('astermind-elm-pro') || f.includes('premium-full') || f.includes('pro-full')); if (features.includes('astermind-elm') || features.includes('astermind-elm-basic') || features.includes('astermind-elm-pro-full') || hasPremiumElmFeature || features.length > 0) { // SECURITY: Verify token is not expired const now = Math.floor(Date.now() / 1000); if (payload.exp && payload.exp < now) { throw new Error('License token has expired. Please renew your license.'); } // SECURITY: Verify that the token was actually validated by license-runtime // If state shows "invalid" with reasons other than audience mismatch, reject it // This ensures fake tokens (invalid signature) are rejected even if payload looks valid if (state.status === 'invalid' && state.reason) { const reason = state.reason.toLowerCase(); // Only accept if the ONLY reason is audience mismatch if (!reason.includes('bad audience') && !reason.includes('audience') && !reason.includes('expected audience')) { // Token failed validation for other reasons (signature, issuer, etc.) - reject throw new Error('License validation failed. Token signature or format is invalid.'); } } // Token is valid, just audience mismatch - accept it for Pro return; } } } // Security: Final check - if state is explicitly invalid, reject if (state.status === 'invalid' && state.reason && !state.reason.includes('Bad audience') && !state.reason.includes('audience')) { throw new Error('License validation failed. Please provide a valid license token.'); } // If nothing works, throw error throw new Error('Valid license required. Please provide a license token for astermind-pro or astermind-elm.'); } /** * Check if license is valid and astermind-pro feature is available (non-blocking). * Also accepts astermind-elm licenses as valid for Pro. * @returns true if astermind-pro or astermind-elm feature is available */ export function checkLicense() { if (!initialized) { initializeLicense(); } // First check for astermind-pro feature (normal case) if (hasFeature('astermind-pro')) { return true; } // If no astermind-pro, check for astermind-elm features if (hasFeature('astermind-elm') || hasFeature('astermind-elm-basic') || hasFeature('astermind-elm-pro-full')) { return true; } // If license-runtime marked token as invalid due to audience mismatch, // but we have an astermind-elm token, check the token payload directly if (currentToken) { const payload = parseTokenPayload(currentToken); if (payload?.aud === 'astermind-elm') { // Check if token has valid features in its payload const features = payload?.features || []; // Accept any premium ELM features (e.g., "astermind-elm-premium-full", "astermind-elm-pro-full") const hasPremiumElmFeature = features.some((f) => f.includes('astermind-elm-premium') || f.includes('astermind-elm-pro') || f.includes('premium-full') || f.includes('pro-full')); if (features.includes('astermind-elm') || features.includes('astermind-elm-basic') || features.includes('astermind-elm-pro-full') || hasPremiumElmFeature || features.length > 0) { // Token is valid, just audience mismatch - accept it for Pro return true; } } } return false; } /** * Check if astermind-synth feature is available (included with Pro subscription). * @returns true if astermind-synth feature is available */ export function checkSynthLicense() { if (!initialized) { initializeLicense(); } return hasFeature('astermind-synth'); } /** * Get detailed license status. * If token has audience "astermind-elm", we'll check if it has valid features * even if the runtime marks it as invalid due to audience mismatch. * @returns LicenseState object with status, reason, payload, etc. */ export function getLicenseStatus() { if (!initialized) { initializeLicense(); } const state = getLicenseState(); // If status is invalid due to audience mismatch but token is for astermind-elm, // check if we have valid features anyway if (state.status === 'invalid' && state.reason === 'Bad audience' && currentToken) { const payload = parseTokenPayload(currentToken); if (payload?.aud === 'astermind-elm') { // Check if token has valid features in payload const features = payload?.features || []; // Accept any premium ELM features (e.g., "astermind-elm-premium-full", "astermind-elm-pro-full") const hasPremiumElmFeature = features.some((f) => f.includes('astermind-elm-premium') || f.includes('astermind-elm-pro') || f.includes('premium-full') || f.includes('pro-full')); if (features.includes('astermind-elm') || features.includes('astermind-elm-basic') || features.includes('astermind-elm-pro-full') || hasPremiumElmFeature || features.length > 0) { // Return a modified state indicating it's valid for Pro return { ...state, status: 'valid', reason: undefined, }; } } } return state; } /** * Parse JWT token to extract payload (without verification). * Used to check audience before validation. */ function parseTokenPayload(token) { try { const parts = token.split('.'); if (parts.length !== 3) { return null; } // JWT tokens use base64url encoding - use the license-runtime utility const payload = base64urlDecodeJson(parts[1]); return payload; } catch (err) { return null; } } /** * Set license token from a string. * This will propagate the license to both astermind-pro and astermind-synth. * Accepts tokens with audience "astermind-elm" or "astermind-pro". * * SECURITY: This function validates the JWT token cryptographically using the license server's * public keys. Fake tokens will be rejected even if they have valid-looking payloads. * * Note: Even if the license-runtime marks astermind-elm tokens as "invalid" due to audience mismatch, * we still accept them for Pro usage IF they pass cryptographic validation. The checkLicense() and * requireLicense() functions will check for astermind-elm features as well. * * Useful for dynamic token loading from backend services or user input. * @param token The license token string (JWT format) */ export async function setLicenseTokenFromString(token) { if (!initialized) { initializeLicense(); } // SECURITY: Basic token format validation before attempting to set if (!token || typeof token !== 'string') { throw new Error('Invalid license token: token must be a non-empty string'); } // SECURITY: Validate JWT format (must have 3 parts separated by dots) const parts = token.split('.'); if (parts.length !== 3) { throw new Error('Invalid license token: token must be a valid JWT format (header.payload.signature)'); } // SECURITY: Validate that payload can be parsed (basic format check) const payload = parseTokenPayload(token); if (!payload) { throw new Error('Invalid license token: unable to parse token payload'); } // SECURITY: Validate required JWT claims exist if (!payload.iss || !payload.aud || !payload.sub) { throw new Error('Invalid license token: missing required claims (iss, aud, sub)'); } // SECURITY: Validate issuer matches expected issuer if (payload.iss !== 'https://license.astermind.ai') { throw new Error('Invalid license token: token issuer does not match expected issuer'); } currentToken = token; // Check token audience - accept both "astermind-elm" and "astermind-pro" const tokenAudience = payload.aud; const isElmToken = tokenAudience === 'astermind-elm'; // SECURITY: Try to set the token - this will cryptographically validate the signature // using the license server's public keys (JWKS). Fake tokens will fail here. try { await setLicenseToken(token); // If setLicenseToken succeeds, the token has passed cryptographic validation // The license-runtime library validates the JWT signature using JWKS from the server } catch (err) { // SECURITY: Only accept audience mismatch errors for astermind-elm tokens // All other errors (invalid signature, expired, etc.) should be rejected if (isElmToken && (err?.message?.includes('Bad audience') || err?.message?.includes('audience'))) { // Token is stored in currentToken, license-runtime may mark it as invalid // but we'll still allow it through checkLicense() and requireLicense() // ONLY if it passed cryptographic validation (which setLicenseToken would have done) console.warn('[Astermind Pro] Token audience is "astermind-elm" - will be accepted for Pro via feature checking'); } else { // SECURITY: Reject all other errors - these include: // - Invalid signature (fake tokens) // - Expired tokens // - Invalid issuer // - Malformed tokens // - Network errors (can't fetch JWKS) throw new Error(`License validation failed: ${err?.message || 'Invalid or fake token'}`); } } // Propagate to astermind-synth if available try { // Dynamically import synth's license module to avoid circular dependencies const synthLicense = await import('@astermind/astermind-synthetic-data'); if (synthLicense && typeof synthLicense.setLicenseTokenFromString === 'function') { // setLicenseTokenFromString will handle initialization internally await synthLicense.setLicenseTokenFromString(token); } } catch (err) { // Synth might not be installed, that's okay console.warn('[Astermind Pro] Could not propagate license to Astermind Synth:', err); } } /** * Get the current license token (if set). * @returns The current license token or null */ export function getCurrentLicenseToken() { return currentToken; } /** * Check if license runtime has been initialized. * @returns true if initializeLicense() has been called */ export function isLicenseInitialized() { return initialized; } //# sourceMappingURL=license.js.map