@astermind/astermind-premium
Version:
Astermind Premium - Premium ML Toolkit
457 lines • 21.3 kB
JavaScript
/**
* License management for Astermind Premium
* Wraps @astermindai/license-runtime with convenience functions
* Also propagates license to Astermind Pro and 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;
// SECURITY: Track whether the current token has passed cryptographic validation
// This prevents fake tokens from being accepted even if they have valid-looking payloads
let tokenValidated = false;
/**
* Initialize the license runtime singleton for Astermind Premium.
* 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-premium (primary)
* - astermind-pro (fallback)
* - astermind-elm (fallback)
* - 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-premium
// Note: We accept "astermind-premium", "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-premium', // Primary audience, but we'll accept others in setLicenseTokenFromString
mode: process.env.NODE_ENV === 'production' ? 'strict' : 'strict', // Always strict for Premium
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 Premium] Failed to load license token from environment:', err);
});
}
}
/**
* Require a valid license (throws if not available).
* Accepts astermind-premium, 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-premium
try {
requireFeature('astermind-premium');
// Additional security: Verify the state is actually valid
if (state.status === 'valid' || hasFeature('astermind-premium')) {
return;
}
}
catch (err) {
// Continue to check other options
}
// 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 token payload
}
// SECURITY: If all feature checks fail, but we have a token,
// CRITICAL: Only accept it if it passed cryptographic validation
// We MUST verify tokenValidated flag to prevent fake tokens from being accepted
if (currentToken && tokenValidated) {
const payload = parseTokenPayload(currentToken);
const validAudiences = ['astermind-premium', 'astermind-pro', 'astermind-elm'];
if (payload?.aud && validAudiences.includes(payload.aud)) {
const features = payload?.features || [];
// Accept any premium ELM features (e.g., "astermind-elm-premium-full")
const hasPremiumElmFeature = features.some((f) => f.includes('astermind-elm-premium') || f.includes('premium-full'));
if (features.includes('astermind-premium') ||
features.includes('astermind-pro') ||
features.includes('astermind-elm') ||
features.includes('astermind-elm-basic') ||
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: Double-check 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
tokenValidated = false; // Reset validation flag
throw new Error('License validation failed. Token signature or format is invalid.');
}
}
// Token passed cryptographic validation, just audience mismatch - accept it for Premium
return;
}
}
}
// SECURITY: If we have a token but it hasn't been validated, reject it
// This prevents fake tokens from being accepted
if (currentToken && !tokenValidated) {
throw new Error('License validation failed. Token has not passed cryptographic validation. Fake tokens are rejected.');
}
// 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-premium, astermind-pro, or astermind-elm.');
}
/**
* Check if license is valid and astermind-premium feature is available (non-blocking).
* Also accepts astermind-pro and astermind-elm licenses as valid for Premium.
* @returns true if astermind-premium, astermind-pro, or astermind-elm feature is available
*/
export function checkLicense() {
if (!initialized) {
initializeLicense();
}
// First check for astermind-premium feature (normal case)
if (hasFeature('astermind-premium')) {
return true;
}
// Check for astermind-pro feature
if (hasFeature('astermind-pro')) {
return true;
}
// If no premium/pro, check for astermind-elm features
if (hasFeature('astermind-elm') || hasFeature('astermind-elm-basic')) {
return true;
}
// SECURITY: If license-runtime marked token as invalid due to audience mismatch,
// but we have a token, check the token payload directly
// CRITICAL: Only accept if token passed cryptographic validation
if (currentToken && tokenValidated) {
const payload = parseTokenPayload(currentToken);
const validAudiences = ['astermind-premium', 'astermind-pro', 'astermind-elm'];
if (payload?.aud && validAudiences.includes(payload.aud)) {
// Check if token has valid features in its payload
const features = payload?.features || [];
// Accept any premium ELM features (e.g., "astermind-elm-premium-full")
const hasPremiumElmFeature = features.some((f) => f.includes('astermind-elm-premium') || f.includes('premium-full'));
if (features.includes('astermind-premium') ||
features.includes('astermind-pro') ||
features.includes('astermind-elm') ||
features.includes('astermind-elm-basic') ||
hasPremiumElmFeature ||
features.length > 0) {
// SECURITY: Verify token is not expired
const now = Math.floor(Date.now() / 1000);
if (payload.exp && payload.exp < now) {
return false;
}
// Token passed cryptographic validation, just audience mismatch - accept it for Premium
return true;
}
}
}
return false;
}
/**
* Check if astermind-synth feature is available (included with Premium 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-pro" or "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();
// SECURITY: If status is invalid due to audience mismatch but token is for a valid audience,
// check if we have valid features anyway
// CRITICAL: Only accept if token passed cryptographic validation
if (state.status === 'invalid' && state.reason === 'Bad audience' && currentToken && tokenValidated) {
const payload = parseTokenPayload(currentToken);
const validAudiences = ['astermind-premium', 'astermind-pro', 'astermind-elm'];
if (payload?.aud && validAudiences.includes(payload.aud)) {
// Check if token has valid features in payload
const features = payload?.features || [];
// Accept any premium ELM features (e.g., "astermind-elm-premium-full")
const hasPremiumElmFeature = features.some((f) => f.includes('astermind-elm-premium') || f.includes('premium-full'));
if (features.includes('astermind-premium') ||
features.includes('astermind-pro') ||
features.includes('astermind-elm') ||
features.includes('astermind-elm-basic') ||
hasPremiumElmFeature ||
features.length > 0) {
// SECURITY: Verify token is not expired
const now = Math.floor(Date.now() / 1000);
if (payload.exp && payload.exp >= now) {
// Return a modified state indicating it's valid for Premium
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 astermind-pro and astermind-synth.
* Accepts tokens with audience "astermind-premium", "astermind-pro", or "astermind-elm".
*
* 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 tokens as "invalid" due to audience mismatch,
* we still accept them for Premium usage IF they pass cryptographic validation. The checkLicense() and
* requireLicense() functions will check for all valid features.
*
* 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');
}
// Check token audience - accept "astermind-premium", "astermind-pro", and "astermind-elm"
const tokenAudience = payload.aud;
const validAudiences = ['astermind-premium', 'astermind-pro', 'astermind-elm'];
const isAcceptedToken = validAudiences.includes(tokenAudience);
// 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.
// CRITICAL: setLicenseToken may not throw - it sets the token and marks it invalid in state
// We MUST check the license state AFTER setting to verify cryptographic validation passed
try {
await setLicenseToken(token);
// SECURITY: Check license state immediately after setting token
// This is where we detect fake tokens - if signature validation failed, state will be invalid
const state = getLicenseState();
// SECURITY: If token failed signature validation, reject it immediately
if (state.status === 'invalid' && state.reason) {
const reason = state.reason.toLowerCase();
// Check if the reason is signature-related (fake token)
const isSignatureError = reason.includes('signature') ||
reason.includes('jose') ||
reason.includes('invalid token') ||
reason.includes('verification failed') ||
reason.includes('cannot verify');
if (isSignatureError) {
// Token failed cryptographic validation - reject fake token
currentToken = null;
tokenValidated = false;
throw new Error(`License validation failed: Invalid token signature. Fake tokens are rejected. Reason: ${state.reason}`);
}
// Check if it's an audience mismatch (token passed signature validation)
const isAudienceError = reason.includes('bad audience') ||
reason.includes('audience') ||
reason.includes('expected audience');
if (isAcceptedToken && isAudienceError) {
// Token passed signature validation but has audience mismatch
// Store it and mark as validated - we'll handle audience in requireLicense/checkLicense
currentToken = token;
tokenValidated = true;
console.warn(`[Astermind Premium] Token audience is "${tokenAudience}" - will be accepted for Premium via feature checking`);
}
else if (!isAudienceError) {
// Token failed validation for other reasons (not signature, not audience)
currentToken = null;
tokenValidated = false;
throw new Error(`License validation failed: ${state.reason || 'Token validation failed'}`);
}
else {
// Invalid audience and not in accepted list
currentToken = null;
tokenValidated = false;
throw new Error(`License validation failed: Invalid token audience. Expected one of: ${validAudiences.join(', ')}`);
}
}
else if (state.status === 'valid') {
// Token passed all validation including signature and audience
currentToken = token;
tokenValidated = true;
}
else {
// Unknown state - reject to be safe
currentToken = null;
tokenValidated = false;
throw new Error(`License validation failed: Unknown validation state`);
}
}
catch (err) {
// If setLicenseToken throws an error, check if it's a network/connection error
const errorMsg = err?.message || '';
// Network errors might be acceptable if we're offline, but signature errors are not
if (errorMsg.includes('network') || errorMsg.includes('fetch') || errorMsg.includes('connection')) {
// Network error - don't store token, but don't throw (might be offline)
currentToken = null;
tokenValidated = false;
throw new Error(`License validation failed: Cannot verify token signature. Network error: ${errorMsg}`);
}
// All other errors should reject the token
currentToken = null;
tokenValidated = false;
throw err;
}
// Propagate to astermind-pro if available
try {
// Dynamically import pro's license module to avoid circular dependencies
const proLicense = await import('@astermind/astermind-pro');
if (proLicense && typeof proLicense.setLicenseTokenFromString === 'function') {
// setLicenseTokenFromString will handle initialization internally
await proLicense.setLicenseTokenFromString(token);
}
}
catch (err) {
// Pro might not be installed, that's okay
console.warn('[Astermind Premium] Could not propagate license to Astermind Pro:', err);
}
// 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 Premium] 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