/** * License validation for RevealUI Pro/Enterprise tiers. * * Edge-compatible: uses the Web Crypto API (`crypto.subtle`) and `jose` * exclusively. Safe to import from any runtime (Node, Edge, browser, * Workers). No `node:crypto` or filesystem dependencies. * * @dependencies * - jose - JWT signing/verification (Web Crypto API) * - zod - Schema validation for license payloads */ import { z } from 'zod'; /** Available license tiers */ export type LicenseTier = 'free' | 'pro' | 'max' | 'enterprise'; /** * License operating mode — determines how the system behaves when license * checks encounter various failure conditions. * * - active: License is valid and current * - grace: License has an issue but is within a grace period (still allowed) * - read-only: Perpetual support lapsed past grace — reads allowed, writes blocked * - expired: Grace period exhausted — degraded to free tier * - invalid: Signature invalid or tampered — hard fail * - missing: No license configured — free tier */ export type LicenseMode = 'active' | 'grace' | 'read-only' | 'expired' | 'invalid' | 'missing'; /** Detailed result from license status check */ export interface LicenseCheckResult { /** Whether the requested action is allowed */ allowed: boolean; /** Current effective tier */ tier: LicenseTier; /** Operating mode */ mode: LicenseMode; /** Human-readable reason for the current mode */ reason?: string; /** Milliseconds remaining in grace period (undefined if not in grace) */ graceRemainingMs?: number; /** Whether writes should be blocked (read-only mode for lapsed perpetual) */ readOnly: boolean; } /** Grace period configuration (in days). Overridable via env for testing. */ export interface GracePeriodConfig { /** Days after subscription expiry before degrading to free (default: 3) */ subscriptionDays: number; /** Days after perpetual support lapse before read-only mode (default: 30) */ perpetualDays: number; /** Days of cached-license grace when infra is unreachable (default: 7) */ infraDays: number; } /** * Configure grace period durations. Useful for testing. */ export declare function configureGracePeriods(overrides: Partial): void; /** Decoded license payload schema */ declare const licensePayloadSchema: z.ZodObject<{ tier: z.ZodEnum<{ max: "max"; pro: "pro"; enterprise: "enterprise"; }>; customerId: z.ZodString; jti: z.ZodString; domains: z.ZodOptional>; maxSites: z.ZodOptional; maxUsers: z.ZodOptional; perpetual: z.ZodOptional; iat: z.ZodOptional; exp: z.ZodOptional; }, z.core.$strip>; export type LicensePayload = z.infer; /** License cache TTL configuration */ export interface LicenseCacheConfig { /** Cache TTL in milliseconds (default: 15 seconds) */ ttlMs: number; } /** * Hard cap on cache TTL. Any env override exceeding this is clamped + warned. * Revoked licenses must not stay cached longer than this, regardless of * operator misconfiguration. 15 minutes balances revocation responsiveness * against DB load for high-traffic deployments. * * Tracked by MASTER_PLAN §CR-8 CR8-P1-05. */ export declare const MAX_LICENSE_CACHE_TTL_MS: number; /** * Parse and validate the `LICENSE_CACHE_TTL_MS` env value. * * Rules: * - Unset / non-numeric / non-positive → `DEFAULT_TTL_MS` (15s) * - Above `MAX_LICENSE_CACHE_TTL_MS` → clamped to cap, warning emitted * - Otherwise → parsed value * * Exported for unit testing. Production code uses the module-load-time * evaluation in `DEFAULT_CACHE_CONFIG` below. */ export declare function parseLicenseCacheTtlEnv(envValue: string | undefined): number; /** * Configure the license cache TTL. * Useful for tests (short TTL) or deployments needing faster revocation detection. */ export declare function configureLicenseCache(overrides: Partial): void; /** * Computes a deterministic Key ID (kid) from a public key PEM string. * Returns the first 8 characters of the SHA-256 hex digest of the PEM. * * Async because it uses `crypto.subtle.digest` for full edge compatibility. */ export declare function computeKeyId(publicKeyPem: string): Promise; /** * Validates a license key JWT and returns the decoded payload. * Returns null if the key is invalid, expired, or missing. * * Phase 1 audit B-2: when `expectedCustomerId` is supplied, the JWT's * `customerId` claim must match exactly — otherwise the token is rejected * even when the signature + iss + aud + exp are all valid. This binds a * license to its purchaser. Forge mode uses this against the env-configured * `REVEALUI_LICENSED_CUSTOMER_ID`. Hosted mode (where the deployment IS the * customer) leaves it undefined. * * Note: `nbf` and `exp` are enforced automatically by jose.jwtVerify against * `currentDate` (defaults to now). `iss` and `aud` are enforced via the * options below. Signature is enforced via the public key. */ export declare function validateLicenseKey(licenseKey: string, publicKey: string, expectedCustomerId?: string): Promise; /** * Initialize the license system. Call once at application startup. * Reads REVEALUI_LICENSE_KEY and REVEALUI_LICENSE_PUBLIC_KEY from environment. * * @returns The resolved license tier */ export declare function initializeLicense(): Promise; /** * Returns the current license tier. * If the license hasn't been initialized or the cache has expired, returns 'free'. */ export declare function getCurrentTier(): LicenseTier; /** * Returns the full license payload, or null if no valid license or cache expired. */ export declare function getLicensePayload(): LicensePayload | null; /** * Returns true when `host` is covered by the license's `domains` claim. * * The single matching primitive for RevForge/Fleet domain-lock — consumed by * the API's `requireDomain` middleware, the admin boot check, and * `validateLicenseAtStartup`. Because the allowed domains come from the signed * JWT `domains` claim (not a separate env var), the lock is cryptographically * bound: it cannot be spoofed by editing an env file. * * Matching rules (no authored regex, per the fleet no-regex rule): * - `host` is lower-cased and stripped of any `:port` suffix * - `localhost` / `127.0.0.1` are always allowed, so a trial kit boots and * serves on its default `http://localhost` regardless of the licensed domain * - otherwise `host` must equal a licensed domain OR be a subdomain of one * (`app.example.com` matches `example.com`) * * @param host raw Host header value or URL hostname (a `:port` suffix is tolerated) * @param domains the license payload's `domains` claim */ export declare function hostMatchesLicensedDomains(host: string, domains: readonly string[]): boolean; /** * Checks whether the current license is at least the given tier. * Also validates that the license has not expired (checks JWT exp claim). * * Subscription grace: if the JWT has expired but is within the configured * grace period (default 3 days), access is still allowed. Use * `getLicenseStatus()` to check whether the license is in grace. */ export declare function isLicensed(requiredTier: LicenseTier): boolean; /** * Returns the full license status including mode, grace state, and read-only flag. * * Use this for UI decisions (banners, warnings) and API response headers. * For simple gate checks, `isLicensed()` is sufficient. */ export declare function getLicenseStatus(requiredTier?: LicenseTier): LicenseCheckResult; /** * Returns the configured grace period durations. * Useful for API response headers and customer-facing documentation. */ export declare function getGraceConfig(): Readonly; /** * Returns the maximum number of sites allowed by the current license. */ export declare function getMaxSites(): number; /** * Returns the maximum number of users/editors allowed by the current license. */ export declare function getMaxUsers(): number; /** * Returns the maximum agent tasks per billing cycle for the current license. * Track B metering: free=1K, pro=10K, max=50K, enterprise=unlimited. */ export declare function getMaxAgentTasks(): number; /** * Generates a signed license key JWT. * Server-only in practice (requires the private key) but edge-compatible — * `jose.importPKCS8` and `SignJWT` both run on Web Crypto. * * @param payload - License payload (tier, customerId, limits, perpetual flag) * @param privateKey - Ed25519 private key (PEM format) * @param expiresInSeconds - JWT expiration in seconds. Pass null for perpetual * licenses (no exp claim). Defaults to 1 year for subscription licenses. * @param publicKey - Ed25519 public key (PEM format). When provided, a `kid` * claim is added to the JWT header for forward-compatible key rotation. * @returns Signed JWT string */ export declare function generateLicenseKey(payload: Omit & { jti?: string; }, privateKey: string, expiresInSeconds?: number | null, publicKey?: string): Promise; /** * Reset license state. Primarily for testing. */ export declare function resetLicenseState(): void; export {}; //# sourceMappingURL=license.d.ts.map