@revealui/core 0.5.6 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/README.md +1 -1
  2. package/dist/api/compression.d.ts +3 -4
  3. package/dist/api/compression.d.ts.map +1 -1
  4. package/dist/api/compression.js +1 -2
  5. package/dist/api/rate-limit.d.ts +10 -11
  6. package/dist/api/rate-limit.d.ts.map +1 -1
  7. package/dist/api/rate-limit.js +9 -16
  8. package/dist/api/response-cache.d.ts +8 -9
  9. package/dist/api/response-cache.d.ts.map +1 -1
  10. package/dist/api/response-cache.js +2 -3
  11. package/dist/client/admin/layout.d.ts.map +1 -1
  12. package/dist/client/admin/layout.js +1 -3
  13. package/dist/client/admin/utils/apiClient.d.ts.map +1 -1
  14. package/dist/client/admin/utils/apiClient.js +16 -1
  15. package/dist/client/richtext/index.d.ts.map +1 -1
  16. package/dist/client/richtext/index.js +0 -1
  17. package/dist/collections/operations/create.d.ts.map +1 -1
  18. package/dist/collections/operations/create.js +23 -5
  19. package/dist/database/universal-postgres.d.ts +14 -0
  20. package/dist/database/universal-postgres.d.ts.map +1 -1
  21. package/dist/database/universal-postgres.js +97 -0
  22. package/dist/fieldTraversal.d.ts +1 -1
  23. package/dist/fieldTraversal.d.ts.map +1 -1
  24. package/dist/generated/types/admin.d.ts +2 -2
  25. package/dist/generated/types/admin.d.ts.map +1 -1
  26. package/dist/index.d.ts +2 -2
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +6 -2
  29. package/dist/instance/methods/create.d.ts.map +1 -1
  30. package/dist/instance/methods/create.js +3 -2
  31. package/dist/license-encryption.d.ts +11 -2
  32. package/dist/license-encryption.d.ts.map +1 -1
  33. package/dist/license-encryption.js +77 -22
  34. package/dist/license.d.ts +106 -8
  35. package/dist/license.d.ts.map +1 -1
  36. package/dist/license.js +243 -35
  37. package/dist/nextjs/index.d.ts +0 -1
  38. package/dist/nextjs/index.d.ts.map +1 -1
  39. package/dist/nextjs/index.js +7 -2
  40. package/dist/nextjs/withRevealUI.d.ts +29 -1
  41. package/dist/nextjs/withRevealUI.d.ts.map +1 -1
  42. package/dist/observability/logger.d.ts +0 -4
  43. package/dist/observability/logger.d.ts.map +1 -1
  44. package/dist/observability/logger.js +2 -29
  45. package/dist/observability/metrics.d.ts +25 -0
  46. package/dist/observability/metrics.d.ts.map +1 -1
  47. package/dist/observability/metrics.js +26 -0
  48. package/dist/revealui.d.ts +0 -5
  49. package/dist/revealui.d.ts.map +1 -1
  50. package/dist/revealui.js +0 -10
  51. package/dist/revforge-license.d.ts +60 -0
  52. package/dist/revforge-license.d.ts.map +1 -0
  53. package/dist/revforge-license.js +74 -0
  54. package/dist/richtext/exports/server/rsc.d.ts +2 -17
  55. package/dist/richtext/exports/server/rsc.d.ts.map +1 -1
  56. package/dist/richtext/exports/server/rsc.js +9 -54
  57. package/dist/types/index.d.ts +1 -1
  58. package/dist/types/index.d.ts.map +1 -1
  59. package/dist/types/{legacy.d.ts → internal.d.ts} +1 -1
  60. package/dist/types/internal.d.ts.map +1 -0
  61. package/dist/types/runtime.d.ts +12 -0
  62. package/dist/types/runtime.d.ts.map +1 -1
  63. package/dist/utils/api-wrapper.d.ts +4 -6
  64. package/dist/utils/api-wrapper.d.ts.map +1 -1
  65. package/dist/utils/api-wrapper.js +8 -10
  66. package/package.json +71 -24
  67. package/dist/types/legacy.d.ts.map +0 -1
  68. /package/dist/types/{legacy.js → internal.js} +0 -0
package/dist/license.d.ts CHANGED
@@ -1,13 +1,57 @@
1
1
  /**
2
2
  * License validation for RevealUI Pro/Enterprise tiers.
3
3
  *
4
+ * Edge-compatible: uses the Web Crypto API (`crypto.subtle`) and `jose`
5
+ * exclusively. Safe to import from any runtime (Node, Edge, browser,
6
+ * Workers). No `node:crypto` or filesystem dependencies.
7
+ *
4
8
  * @dependencies
5
- * - jose - JWT token verification (Web Crypto API)
9
+ * - jose - JWT signing/verification (Web Crypto API)
6
10
  * - zod - Schema validation for license payloads
7
11
  */
8
12
  import { z } from 'zod';
9
13
  /** Available license tiers */
10
14
  export type LicenseTier = 'free' | 'pro' | 'max' | 'enterprise';
15
+ /**
16
+ * License operating mode — determines how the system behaves when license
17
+ * checks encounter various failure conditions.
18
+ *
19
+ * - active: License is valid and current
20
+ * - grace: License has an issue but is within a grace period (still allowed)
21
+ * - read-only: Perpetual support lapsed past grace — reads allowed, writes blocked
22
+ * - expired: Grace period exhausted — degraded to free tier
23
+ * - invalid: Signature invalid or tampered — hard fail
24
+ * - missing: No license configured — free tier
25
+ */
26
+ export type LicenseMode = 'active' | 'grace' | 'read-only' | 'expired' | 'invalid' | 'missing';
27
+ /** Detailed result from license status check */
28
+ export interface LicenseCheckResult {
29
+ /** Whether the requested action is allowed */
30
+ allowed: boolean;
31
+ /** Current effective tier */
32
+ tier: LicenseTier;
33
+ /** Operating mode */
34
+ mode: LicenseMode;
35
+ /** Human-readable reason for the current mode */
36
+ reason?: string;
37
+ /** Milliseconds remaining in grace period (undefined if not in grace) */
38
+ graceRemainingMs?: number;
39
+ /** Whether writes should be blocked (read-only mode for lapsed perpetual) */
40
+ readOnly: boolean;
41
+ }
42
+ /** Grace period configuration (in days). Overridable via env for testing. */
43
+ export interface GracePeriodConfig {
44
+ /** Days after subscription expiry before degrading to free (default: 3) */
45
+ subscriptionDays: number;
46
+ /** Days after perpetual support lapse before read-only mode (default: 30) */
47
+ perpetualDays: number;
48
+ /** Days of cached-license grace when infra is unreachable (default: 7) */
49
+ infraDays: number;
50
+ }
51
+ /**
52
+ * Configure grace period durations. Useful for testing.
53
+ */
54
+ export declare function configureGracePeriods(overrides: Partial<GracePeriodConfig>): void;
11
55
  /** Decoded license payload schema */
12
56
  declare const licensePayloadSchema: z.ZodObject<{
13
57
  tier: z.ZodEnum<{
@@ -16,6 +60,7 @@ declare const licensePayloadSchema: z.ZodObject<{
16
60
  enterprise: "enterprise";
17
61
  }>;
18
62
  customerId: z.ZodString;
63
+ jti: z.ZodString;
19
64
  domains: z.ZodOptional<z.ZodArray<z.ZodString>>;
20
65
  maxSites: z.ZodOptional<z.ZodNumber>;
21
66
  maxUsers: z.ZodOptional<z.ZodNumber>;
@@ -26,9 +71,30 @@ declare const licensePayloadSchema: z.ZodObject<{
26
71
  export type LicensePayload = z.infer<typeof licensePayloadSchema>;
27
72
  /** License cache TTL configuration */
28
73
  export interface LicenseCacheConfig {
29
- /** Cache TTL in milliseconds (default: 24 hours) */
74
+ /** Cache TTL in milliseconds (default: 15 seconds) */
30
75
  ttlMs: number;
31
76
  }
77
+ /**
78
+ * Hard cap on cache TTL. Any env override exceeding this is clamped + warned.
79
+ * Revoked licenses must not stay cached longer than this, regardless of
80
+ * operator misconfiguration. 15 minutes balances revocation responsiveness
81
+ * against DB load for high-traffic deployments.
82
+ *
83
+ * Tracked by MASTER_PLAN §CR-8 CR8-P1-05.
84
+ */
85
+ export declare const MAX_LICENSE_CACHE_TTL_MS: number;
86
+ /**
87
+ * Parse and validate the `LICENSE_CACHE_TTL_MS` env value.
88
+ *
89
+ * Rules:
90
+ * - Unset / non-numeric / non-positive → `DEFAULT_TTL_MS` (15s)
91
+ * - Above `MAX_LICENSE_CACHE_TTL_MS` → clamped to cap, warning emitted
92
+ * - Otherwise → parsed value
93
+ *
94
+ * Exported for unit testing. Production code uses the module-load-time
95
+ * evaluation in `DEFAULT_CACHE_CONFIG` below.
96
+ */
97
+ export declare function parseLicenseCacheTtlEnv(envValue: string | undefined): number;
32
98
  /**
33
99
  * Configure the license cache TTL.
34
100
  * Useful for tests (short TTL) or deployments needing faster revocation detection.
@@ -37,13 +103,26 @@ export declare function configureLicenseCache(overrides: Partial<LicenseCacheCon
37
103
  /**
38
104
  * Computes a deterministic Key ID (kid) from a public key PEM string.
39
105
  * Returns the first 8 characters of the SHA-256 hex digest of the PEM.
106
+ *
107
+ * Async because it uses `crypto.subtle.digest` for full edge compatibility.
40
108
  */
41
- export declare function computeKeyId(publicKeyPem: string): string;
109
+ export declare function computeKeyId(publicKeyPem: string): Promise<string>;
42
110
  /**
43
111
  * Validates a license key JWT and returns the decoded payload.
44
112
  * Returns null if the key is invalid, expired, or missing.
113
+ *
114
+ * Phase 1 audit B-2: when `expectedCustomerId` is supplied, the JWT's
115
+ * `customerId` claim must match exactly — otherwise the token is rejected
116
+ * even when the signature + iss + aud + exp are all valid. This binds a
117
+ * license to its purchaser. Forge mode uses this against the env-configured
118
+ * `REVEALUI_LICENSED_CUSTOMER_ID`. Hosted mode (where the deployment IS the
119
+ * customer) leaves it undefined.
120
+ *
121
+ * Note: `nbf` and `exp` are enforced automatically by jose.jwtVerify against
122
+ * `currentDate` (defaults to now). `iss` and `aud` are enforced via the
123
+ * options below. Signature is enforced via the public key.
45
124
  */
46
- export declare function validateLicenseKey(licenseKey: string, publicKey: string): Promise<LicensePayload | null>;
125
+ export declare function validateLicenseKey(licenseKey: string, publicKey: string, expectedCustomerId?: string): Promise<LicensePayload | null>;
47
126
  /**
48
127
  * Initialize the license system. Call once at application startup.
49
128
  * Reads REVEALUI_LICENSE_KEY and REVEALUI_LICENSE_PUBLIC_KEY from environment.
@@ -63,8 +142,24 @@ export declare function getLicensePayload(): LicensePayload | null;
63
142
  /**
64
143
  * Checks whether the current license is at least the given tier.
65
144
  * Also validates that the license has not expired (checks JWT exp claim).
145
+ *
146
+ * Subscription grace: if the JWT has expired but is within the configured
147
+ * grace period (default 3 days), access is still allowed. Use
148
+ * `getLicenseStatus()` to check whether the license is in grace.
66
149
  */
67
150
  export declare function isLicensed(requiredTier: LicenseTier): boolean;
151
+ /**
152
+ * Returns the full license status including mode, grace state, and read-only flag.
153
+ *
154
+ * Use this for UI decisions (banners, warnings) and API response headers.
155
+ * For simple gate checks, `isLicensed()` is sufficient.
156
+ */
157
+ export declare function getLicenseStatus(requiredTier?: LicenseTier): LicenseCheckResult;
158
+ /**
159
+ * Returns the configured grace period durations.
160
+ * Useful for API response headers and customer-facing documentation.
161
+ */
162
+ export declare function getGraceConfig(): Readonly<GracePeriodConfig>;
68
163
  /**
69
164
  * Returns the maximum number of sites allowed by the current license.
70
165
  */
@@ -80,17 +175,20 @@ export declare function getMaxUsers(): number;
80
175
  export declare function getMaxAgentTasks(): number;
81
176
  /**
82
177
  * Generates a signed license key JWT.
83
- * This is a server-only function - requires the private key.
178
+ * Server-only in practice (requires the private key) but edge-compatible —
179
+ * `jose.importPKCS8` and `SignJWT` both run on Web Crypto.
84
180
  *
85
181
  * @param payload - License payload (tier, customerId, limits, perpetual flag)
86
- * @param privateKey - RS256 private key (PEM format)
182
+ * @param privateKey - Ed25519 private key (PEM format)
87
183
  * @param expiresInSeconds - JWT expiration in seconds. Pass null for perpetual
88
184
  * licenses (no exp claim). Defaults to 1 year for subscription licenses.
89
- * @param publicKey - RS256 public key (PEM format). When provided, a `kid`
185
+ * @param publicKey - Ed25519 public key (PEM format). When provided, a `kid`
90
186
  * claim is added to the JWT header for forward-compatible key rotation.
91
187
  * @returns Signed JWT string
92
188
  */
93
- export declare function generateLicenseKey(payload: Omit<LicensePayload, 'iat' | 'exp'>, privateKey: string, expiresInSeconds?: number | null, publicKey?: string): Promise<string>;
189
+ export declare function generateLicenseKey(payload: Omit<LicensePayload, 'iat' | 'exp' | 'jti'> & {
190
+ jti?: string;
191
+ }, privateKey: string, expiresInSeconds?: number | null, publicKey?: string): Promise<string>;
94
192
  /**
95
193
  * Reset license state. Primarily for testing.
96
194
  */
@@ -1 +1 @@
1
- {"version":3,"file":"license.d.ts","sourceRoot":"","sources":["../src/license.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,8BAA8B;AAC9B,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,YAAY,CAAC;AAEhE,qCAAqC;AACrC,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;iBAqBxB,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAElE,sCAAsC;AACtC,MAAM,WAAW,kBAAkB;IACjC,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;CACf;AAkBD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,OAAO,CAAC,kBAAkB,CAAC,GAAG,IAAI,CAElF;AAoCD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAEzD;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CA0BhC;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,WAAW,CAAC,CAkC9D;AAaD;;;GAGG;AACH,wBAAgB,cAAc,IAAI,WAAW,CAG5C;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,cAAc,GAAG,IAAI,CAGzD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,YAAY,EAAE,WAAW,GAAG,OAAO,CAqB7D;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAMpC;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAMpC;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAMzC;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,KAAK,CAAC,EAC5C,UAAU,EAAE,MAAM,EAClB,gBAAgB,GAAE,MAAM,GAAG,IAAyB,EACpD,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CAYjB;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAGxC"}
1
+ {"version":3,"file":"license.d.ts","sourceRoot":"","sources":["../src/license.ts"],"names":[],"mappings":"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"}
package/dist/license.js CHANGED
@@ -1,21 +1,56 @@
1
1
  /**
2
2
  * License validation for RevealUI Pro/Enterprise tiers.
3
3
  *
4
+ * Edge-compatible: uses the Web Crypto API (`crypto.subtle`) and `jose`
5
+ * exclusively. Safe to import from any runtime (Node, Edge, browser,
6
+ * Workers). No `node:crypto` or filesystem dependencies.
7
+ *
4
8
  * @dependencies
5
- * - jose - JWT token verification (Web Crypto API)
9
+ * - jose - JWT signing/verification (Web Crypto API)
6
10
  * - zod - Schema validation for license payloads
7
11
  */
8
- import { createHash } from 'node:crypto';
9
- import { decodeProtectedHeader, importPKCS8, importSPKI, jwtVerify, SignJWT } from 'jose';
12
+ // jose is imported lazily inside async functions to avoid Turbopack's
13
+ // async module initialization ordering issue (see #399). Top-level
14
+ // import of jose triggers an asyncModule wrapper that can race with
15
+ // other modules in the auth route bundle during page data collection.
10
16
  import { z } from 'zod';
11
17
  import { decryptLicenseKey } from './license-encryption.js';
12
18
  import { logger } from './utils/logger.js';
19
+ async function getJose() {
20
+ return await import('jose');
21
+ }
22
+ /** JWT issuer and audience for license tokens — prevents cross-environment replay */
23
+ const LICENSE_ISSUER = process.env.REVEALUI_LICENSE_ISSUER ?? 'https://revealui.com';
24
+ const LICENSE_AUDIENCE = process.env.REVEALUI_LICENSE_AUDIENCE ?? 'revealui-license';
25
+ const DEFAULT_GRACE = {
26
+ subscriptionDays: parseEnvInt('LICENSE_GRACE_SUBSCRIPTION_DAYS', 3),
27
+ perpetualDays: parseEnvInt('LICENSE_GRACE_PERPETUAL_DAYS', 30),
28
+ infraDays: parseEnvInt('LICENSE_GRACE_INFRA_DAYS', 7),
29
+ };
30
+ function parseEnvInt(key, fallback) {
31
+ const val = process.env[key];
32
+ if (val) {
33
+ const parsed = Number.parseInt(val, 10);
34
+ if (Number.isFinite(parsed) && parsed >= 0)
35
+ return parsed;
36
+ }
37
+ return fallback;
38
+ }
39
+ let graceConfig = { ...DEFAULT_GRACE };
40
+ /**
41
+ * Configure grace period durations. Useful for testing.
42
+ */
43
+ export function configureGracePeriods(overrides) {
44
+ graceConfig = { ...DEFAULT_GRACE, ...overrides };
45
+ }
13
46
  /** Decoded license payload schema */
14
47
  const licensePayloadSchema = z.object({
15
48
  /** License tier */
16
49
  tier: z.enum(['pro', 'max', 'enterprise']),
17
- /** Organization or customer ID */
18
- customerId: z.string(),
50
+ /** Organization or customer ID — must be non-empty; used to bind the token to a specific customer */
51
+ customerId: z.string().min(1),
52
+ /** JWT ID — used for per-token revocation; every issued token must carry one */
53
+ jti: z.string().min(1),
19
54
  /** Licensed domain(s) */
20
55
  domains: z.array(z.string()).optional(),
21
56
  /** Maximum number of sites allowed */
@@ -34,16 +69,40 @@ const licensePayloadSchema = z.object({
34
69
  exp: z.number().optional(),
35
70
  });
36
71
  const DEFAULT_TTL_MS = 15_000; // 15 seconds - revoked licenses lose access quickly
37
- const DEFAULT_CACHE_CONFIG = {
38
- ttlMs: (() => {
39
- const envTtl = process.env.LICENSE_CACHE_TTL_MS;
40
- if (envTtl) {
41
- const parsed = Number.parseInt(envTtl, 10);
42
- if (Number.isFinite(parsed) && parsed > 0)
43
- return parsed;
44
- }
72
+ /**
73
+ * Hard cap on cache TTL. Any env override exceeding this is clamped + warned.
74
+ * Revoked licenses must not stay cached longer than this, regardless of
75
+ * operator misconfiguration. 15 minutes balances revocation responsiveness
76
+ * against DB load for high-traffic deployments.
77
+ *
78
+ * Tracked by MASTER_PLAN §CR-8 CR8-P1-05.
79
+ */
80
+ export const MAX_LICENSE_CACHE_TTL_MS = 15 * 60 * 1000;
81
+ /**
82
+ * Parse and validate the `LICENSE_CACHE_TTL_MS` env value.
83
+ *
84
+ * Rules:
85
+ * - Unset / non-numeric / non-positive → `DEFAULT_TTL_MS` (15s)
86
+ * - Above `MAX_LICENSE_CACHE_TTL_MS` → clamped to cap, warning emitted
87
+ * - Otherwise → parsed value
88
+ *
89
+ * Exported for unit testing. Production code uses the module-load-time
90
+ * evaluation in `DEFAULT_CACHE_CONFIG` below.
91
+ */
92
+ export function parseLicenseCacheTtlEnv(envValue) {
93
+ if (!envValue)
94
+ return DEFAULT_TTL_MS;
95
+ const parsed = Number.parseInt(envValue, 10);
96
+ if (!Number.isFinite(parsed) || parsed <= 0)
45
97
  return DEFAULT_TTL_MS;
46
- })(),
98
+ if (parsed > MAX_LICENSE_CACHE_TTL_MS) {
99
+ logger.warn(`LICENSE_CACHE_TTL_MS=${parsed} exceeds the ${MAX_LICENSE_CACHE_TTL_MS}ms (15-minute) cap; using ${MAX_LICENSE_CACHE_TTL_MS}. Longer TTLs extend the window where revoked licenses retain access and are not permitted.`);
100
+ return MAX_LICENSE_CACHE_TTL_MS;
101
+ }
102
+ return parsed;
103
+ }
104
+ const DEFAULT_CACHE_CONFIG = {
105
+ ttlMs: parseLicenseCacheTtlEnv(process.env.LICENSE_CACHE_TTL_MS),
47
106
  };
48
107
  let cacheConfig = { ...DEFAULT_CACHE_CONFIG };
49
108
  let cachedAt = 0;
@@ -58,6 +117,7 @@ let cachedState = {
58
117
  tier: 'free',
59
118
  payload: null,
60
119
  validatedAt: null,
120
+ keyPresentButInvalid: false,
61
121
  };
62
122
  /**
63
123
  * The public key used to verify license JWTs.
@@ -73,7 +133,7 @@ function getPublicKey() {
73
133
  * Reads the license key from environment.
74
134
  * Supports encrypted keys (enc:iv:ciphertext:tag format) via REVEALUI_LICENSE_ENCRYPTION_KEY.
75
135
  */
76
- function getLicenseKey() {
136
+ async function getLicenseKey() {
77
137
  const raw = process.env.REVEALUI_LICENSE_KEY ?? null;
78
138
  if (!raw)
79
139
  return null;
@@ -82,31 +142,67 @@ function getLicenseKey() {
82
142
  /**
83
143
  * Computes a deterministic Key ID (kid) from a public key PEM string.
84
144
  * Returns the first 8 characters of the SHA-256 hex digest of the PEM.
145
+ *
146
+ * Async because it uses `crypto.subtle.digest` for full edge compatibility.
85
147
  */
86
- export function computeKeyId(publicKeyPem) {
87
- return createHash('sha256').update(publicKeyPem).digest('hex').slice(0, 8);
148
+ export async function computeKeyId(publicKeyPem) {
149
+ const encoded = new TextEncoder().encode(publicKeyPem);
150
+ const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', encoded));
151
+ let hex = '';
152
+ // Only the first 4 bytes (8 hex chars) — enough to identify rotated keys.
153
+ for (const b of digest.subarray(0, 4)) {
154
+ hex += b.toString(16).padStart(2, '0');
155
+ }
156
+ return hex;
88
157
  }
89
158
  /**
90
159
  * Validates a license key JWT and returns the decoded payload.
91
160
  * Returns null if the key is invalid, expired, or missing.
161
+ *
162
+ * Phase 1 audit B-2: when `expectedCustomerId` is supplied, the JWT's
163
+ * `customerId` claim must match exactly — otherwise the token is rejected
164
+ * even when the signature + iss + aud + exp are all valid. This binds a
165
+ * license to its purchaser. Forge mode uses this against the env-configured
166
+ * `REVEALUI_LICENSED_CUSTOMER_ID`. Hosted mode (where the deployment IS the
167
+ * customer) leaves it undefined.
168
+ *
169
+ * Note: `nbf` and `exp` are enforced automatically by jose.jwtVerify against
170
+ * `currentDate` (defaults to now). `iss` and `aud` are enforced via the
171
+ * options below. Signature is enforced via the public key.
92
172
  */
93
- export async function validateLicenseKey(licenseKey, publicKey) {
173
+ export async function validateLicenseKey(licenseKey, publicKey, expectedCustomerId) {
94
174
  try {
175
+ const jose = await getJose();
95
176
  // Extract kid from JWT header for forward-compatible key rotation
96
- const header = decodeProtectedHeader(licenseKey);
97
- const expectedKid = computeKeyId(publicKey);
177
+ const header = jose.decodeProtectedHeader(licenseKey);
178
+ const expectedKid = await computeKeyId(publicKey);
98
179
  if (header.kid && header.kid !== expectedKid) {
99
180
  logger.warn(`JWT kid mismatch: token has "${header.kid}", current key is "${expectedKid}". ` +
100
181
  'Token may have been signed with a rotated key.');
101
182
  }
102
- const key = await importSPKI(publicKey, 'RS256');
103
- const { payload } = await jwtVerify(licenseKey, key, {
104
- algorithms: ['RS256', 'ES256'],
183
+ const key = await jose.importSPKI(publicKey, 'EdDSA');
184
+ // Accept tokens expired within the subscription grace window so the
185
+ // payload is available for grace-period calculations in isLicensed().
186
+ const { payload } = await jose.jwtVerify(licenseKey, key, {
187
+ algorithms: ['EdDSA'],
188
+ clockTolerance: graceConfig.subscriptionDays * 86_400,
189
+ issuer: LICENSE_ISSUER,
190
+ audience: LICENSE_AUDIENCE,
105
191
  });
106
192
  const result = licensePayloadSchema.safeParse(payload);
107
193
  if (!result.success) {
108
194
  return null;
109
195
  }
196
+ // Phase 1 audit B-2: customerId binding. If the caller supplied an
197
+ // expectation, the JWT's customerId must match exactly. This prevents
198
+ // a leaked JWT from one customer being used to license another's
199
+ // deployment (Forge customer-binding) or a leaked stamping-time JWT
200
+ // being replayed against a deployment expecting a different customer.
201
+ if (expectedCustomerId !== undefined && result.data.customerId !== expectedCustomerId) {
202
+ logger.warn(`License customerId mismatch: token has "${result.data.customerId}", ` +
203
+ `deployment expects "${expectedCustomerId}". Token rejected.`);
204
+ return null;
205
+ }
110
206
  return result.data;
111
207
  }
112
208
  catch {
@@ -120,16 +216,27 @@ export async function validateLicenseKey(licenseKey, publicKey) {
120
216
  * @returns The resolved license tier
121
217
  */
122
218
  export async function initializeLicense() {
123
- const licenseKey = getLicenseKey();
219
+ const licenseKey = await getLicenseKey();
124
220
  const publicKey = getPublicKey();
125
221
  if (!(licenseKey && publicKey)) {
126
- cachedState = { tier: 'free', payload: null, validatedAt: Date.now() };
222
+ cachedState = {
223
+ tier: 'free',
224
+ payload: null,
225
+ validatedAt: Date.now(),
226
+ keyPresentButInvalid: false,
227
+ };
127
228
  cachedAt = Date.now();
128
229
  return 'free';
129
230
  }
130
231
  const payload = await validateLicenseKey(licenseKey, publicKey);
131
232
  if (!payload) {
132
- cachedState = { tier: 'free', payload: null, validatedAt: Date.now() };
233
+ // Key was present but failed validation (expired beyond grace, invalid signature, etc.)
234
+ cachedState = {
235
+ tier: 'free',
236
+ payload: null,
237
+ validatedAt: Date.now(),
238
+ keyPresentButInvalid: true,
239
+ };
133
240
  cachedAt = Date.now();
134
241
  return 'free';
135
242
  }
@@ -137,6 +244,7 @@ export async function initializeLicense() {
137
244
  tier: payload.tier,
138
245
  payload,
139
246
  validatedAt: Date.now(),
247
+ keyPresentButInvalid: false,
140
248
  };
141
249
  cachedAt = Date.now();
142
250
  // Clamp cache TTL to license expiry so revoked licenses don't survive the full TTL
@@ -154,7 +262,7 @@ export async function initializeLicense() {
154
262
  */
155
263
  function evictStaleCache() {
156
264
  if (cachedAt > 0 && Date.now() - cachedAt > cacheConfig.ttlMs) {
157
- cachedState = { tier: 'free', payload: null, validatedAt: null };
265
+ cachedState = { tier: 'free', payload: null, validatedAt: null, keyPresentButInvalid: false };
158
266
  cachedAt = 0;
159
267
  }
160
268
  }
@@ -176,6 +284,10 @@ export function getLicensePayload() {
176
284
  /**
177
285
  * Checks whether the current license is at least the given tier.
178
286
  * Also validates that the license has not expired (checks JWT exp claim).
287
+ *
288
+ * Subscription grace: if the JWT has expired but is within the configured
289
+ * grace period (default 3 days), access is still allowed. Use
290
+ * `getLicenseStatus()` to check whether the license is in grace.
179
291
  */
180
292
  export function isLicensed(requiredTier) {
181
293
  evictStaleCache();
@@ -192,11 +304,90 @@ export function isLicensed(requiredTier) {
192
304
  if (!cachedState.payload?.perpetual && cachedState.payload?.exp) {
193
305
  const nowSeconds = Math.floor(Date.now() / 1000);
194
306
  if (cachedState.payload.exp < nowSeconds) {
307
+ // Expired — check subscription grace period
308
+ const graceEndSeconds = cachedState.payload.exp + graceConfig.subscriptionDays * 86_400;
309
+ if (nowSeconds < graceEndSeconds) {
310
+ // Within grace — still allowed, but callers should check getLicenseStatus()
311
+ return tierRank[cachedState.tier] >= tierRank[requiredTier];
312
+ }
195
313
  return false;
196
314
  }
197
315
  }
198
316
  return tierRank[cachedState.tier] >= tierRank[requiredTier];
199
317
  }
318
+ /**
319
+ * Returns the full license status including mode, grace state, and read-only flag.
320
+ *
321
+ * Use this for UI decisions (banners, warnings) and API response headers.
322
+ * For simple gate checks, `isLicensed()` is sufficient.
323
+ */
324
+ export function getLicenseStatus(requiredTier = 'pro') {
325
+ evictStaleCache();
326
+ const tierRank = {
327
+ free: 0,
328
+ pro: 1,
329
+ max: 2,
330
+ enterprise: 3,
331
+ };
332
+ // No license configured — or key was present but failed validation
333
+ if (!cachedState.payload) {
334
+ if (cachedState.keyPresentButInvalid) {
335
+ return {
336
+ allowed: requiredTier === 'free',
337
+ tier: 'free',
338
+ mode: 'expired',
339
+ reason: 'License key failed validation (expired beyond grace or invalid)',
340
+ readOnly: false,
341
+ };
342
+ }
343
+ return {
344
+ allowed: requiredTier === 'free',
345
+ tier: 'free',
346
+ mode: 'missing',
347
+ reason: 'No license configured',
348
+ readOnly: false,
349
+ };
350
+ }
351
+ const nowSeconds = Math.floor(Date.now() / 1000);
352
+ // Check subscription expiry + grace
353
+ if (!cachedState.payload.perpetual && cachedState.payload.exp) {
354
+ if (cachedState.payload.exp < nowSeconds) {
355
+ const graceEndSeconds = cachedState.payload.exp + graceConfig.subscriptionDays * 86_400;
356
+ if (nowSeconds < graceEndSeconds) {
357
+ const graceRemainingMs = (graceEndSeconds - nowSeconds) * 1000;
358
+ return {
359
+ allowed: tierRank[cachedState.tier] >= tierRank[requiredTier],
360
+ tier: cachedState.tier,
361
+ mode: 'grace',
362
+ reason: `Subscription expired, ${Math.ceil(graceRemainingMs / 86_400_000)}-day grace remaining`,
363
+ graceRemainingMs,
364
+ readOnly: false,
365
+ };
366
+ }
367
+ return {
368
+ allowed: requiredTier === 'free',
369
+ tier: 'free',
370
+ mode: 'expired',
371
+ reason: 'Subscription expired and grace period exhausted',
372
+ readOnly: false,
373
+ };
374
+ }
375
+ }
376
+ // Active license
377
+ return {
378
+ allowed: tierRank[cachedState.tier] >= tierRank[requiredTier],
379
+ tier: cachedState.tier,
380
+ mode: 'active',
381
+ readOnly: false,
382
+ };
383
+ }
384
+ /**
385
+ * Returns the configured grace period durations.
386
+ * Useful for API response headers and customer-facing documentation.
387
+ */
388
+ export function getGraceConfig() {
389
+ return graceConfig;
390
+ }
200
391
  /**
201
392
  * Returns the maximum number of sites allowed by the current license.
202
393
  */
@@ -239,24 +430,41 @@ export function getMaxAgentTasks() {
239
430
  }
240
431
  /**
241
432
  * Generates a signed license key JWT.
242
- * This is a server-only function - requires the private key.
433
+ * Server-only in practice (requires the private key) but edge-compatible —
434
+ * `jose.importPKCS8` and `SignJWT` both run on Web Crypto.
243
435
  *
244
436
  * @param payload - License payload (tier, customerId, limits, perpetual flag)
245
- * @param privateKey - RS256 private key (PEM format)
437
+ * @param privateKey - Ed25519 private key (PEM format)
246
438
  * @param expiresInSeconds - JWT expiration in seconds. Pass null for perpetual
247
439
  * licenses (no exp claim). Defaults to 1 year for subscription licenses.
248
- * @param publicKey - RS256 public key (PEM format). When provided, a `kid`
440
+ * @param publicKey - Ed25519 public key (PEM format). When provided, a `kid`
249
441
  * claim is added to the JWT header for forward-compatible key rotation.
250
442
  * @returns Signed JWT string
251
443
  */
252
444
  export async function generateLicenseKey(payload, privateKey, expiresInSeconds = 365 * 24 * 60 * 60, publicKey) {
253
- const key = await importPKCS8(privateKey, 'RS256');
254
- const kid = publicKey ? computeKeyId(publicKey) : undefined;
255
- const header = { alg: 'RS256' };
445
+ const jose = await getJose();
446
+ const key = await jose.importPKCS8(privateKey, 'EdDSA');
447
+ const kid = publicKey ? await computeKeyId(publicKey) : undefined;
448
+ const header = { alg: 'EdDSA' };
256
449
  if (kid) {
257
450
  header.kid = kid;
258
451
  }
259
- const builder = new SignJWT({ ...payload }).setProtectedHeader(header).setIssuedAt();
452
+ // Phase 1 audit B-2: every issued token carries a `jti` so it can be
453
+ // individually revoked without rotating the vendor key. Auto-generate
454
+ // when caller doesn't supply one (the common case).
455
+ const jti = payload.jti ?? crypto.randomUUID();
456
+ // Strip the optional jti from the spread so jose.setJti() is the single
457
+ // source of the claim (avoids a duplicate field in the payload).
458
+ const { jti: _ignoredJti, ...rest } = payload;
459
+ const builder = new jose.SignJWT({ ...rest })
460
+ .setProtectedHeader(header)
461
+ .setIssuedAt()
462
+ // Phase 1 audit B-2: enforce nbf so tokens cannot be replayed pre-issue
463
+ // by a clock-skewed client.
464
+ .setNotBefore('0s')
465
+ .setJti(jti)
466
+ .setIssuer(LICENSE_ISSUER)
467
+ .setAudience(LICENSE_AUDIENCE);
260
468
  if (expiresInSeconds !== null) {
261
469
  builder.setExpirationTime(`${expiresInSeconds}s`);
262
470
  }
@@ -266,6 +474,6 @@ export async function generateLicenseKey(payload, privateKey, expiresInSeconds =
266
474
  * Reset license state. Primarily for testing.
267
475
  */
268
476
  export function resetLicenseState() {
269
- cachedState = { tier: 'free', payload: null, validatedAt: null };
477
+ cachedState = { tier: 'free', payload: null, validatedAt: null, keyPresentButInvalid: false };
270
478
  cachedAt = 0;
271
479
  }
@@ -1,4 +1,3 @@
1
1
  export { getRevealUI } from './utilities.js';
2
2
  export type { WithRevealUIOptions } from './withRevealUI.js';
3
- export { withRevealUI } from './withRevealUI.js';
4
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/nextjs/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,YAAY,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/nextjs/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,YAAY,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -1,3 +1,8 @@
1
- // RevealUI Next.js integration
1
+ // RevealUI Next.js runtime integration.
2
+ //
3
+ // This barrel is intentionally runtime-only. `withRevealUI` is NOT re-exported
4
+ // here because it pulls in `node:fs` + `node:path` at module load, which Next's
5
+ // NFT tracer then attributes to every route that transitively imports this
6
+ // barrel (even via type-only paths). Config-time consumers should import it
7
+ // directly from `@revealui/core/nextjs/withRevealUI`.
2
8
  export { getRevealUI } from './utilities.js';
3
- export { withRevealUI } from './withRevealUI.js';
@@ -1,4 +1,31 @@
1
- import type { NextConfig } from 'next';
1
+ /**
2
+ * Subset of Next.js config shape used by withRevealUI.
3
+ * Defined locally to avoid requiring `next` as a dependency of @revealui/core.
4
+ * Consumers pass their full NextConfig through; we only access these fields.
5
+ */
6
+ interface NextConfig {
7
+ env?: Record<string, string | undefined>;
8
+ webpack?: (config: Record<string, unknown>, context: {
9
+ isServer: boolean;
10
+ dev: boolean;
11
+ dir: string;
12
+ [key: string]: unknown;
13
+ }) => Record<string, unknown>;
14
+ turbopack?: {
15
+ resolveAlias?: Record<string, string>;
16
+ };
17
+ headers?: () => Promise<Array<{
18
+ source: string;
19
+ headers: Array<{
20
+ key: string;
21
+ value: string;
22
+ }>;
23
+ }>>;
24
+ images?: {
25
+ remotePatterns?: Array<Record<string, unknown>>;
26
+ };
27
+ [key: string]: unknown;
28
+ }
2
29
  export interface WithRevealUIOptions {
3
30
  /** Path to the RevealUI config file (relative to Next.js project root) */
4
31
  configPath?: string;
@@ -17,4 +44,5 @@ export interface WithRevealUIOptions {
17
44
  * The alias works with both Webpack (Next.js < 15) and Turbopack (Next.js 16+).
18
45
  */
19
46
  export declare function withRevealUI(nextConfig?: NextConfig, options?: WithRevealUIOptions): NextConfig;
47
+ export {};
20
48
  //# sourceMappingURL=withRevealUI.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"withRevealUI.d.ts","sourceRoot":"","sources":["../../src/nextjs/withRevealUI.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAOvC,MAAM,WAAW,mBAAmB;IAClC,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iCAAiC;IACjC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,uBAAuB;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,UAAU,GAAE,UAAe,EAC3B,OAAO,GAAE,mBAAwB,GAChC,UAAU,CAgMZ"}
1
+ {"version":3,"file":"withRevealUI.d.ts","sourceRoot":"","sources":["../../src/nextjs/withRevealUI.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,UAAU,UAAU;IAClB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACzC,OAAO,CAAC,EAAE,CACR,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,OAAO,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,OAAO,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,KAC9E,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC7B,SAAS,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;IACtD,OAAO,CAAC,EAAE,MAAM,OAAO,CACrB,KAAK,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,KAAK,CAAC;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,KAAK,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAAE,CAAC,CAC1E,CAAC;IACF,MAAM,CAAC,EAAE;QAAE,cAAc,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;KAAE,CAAC;IAC7D,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAOD,MAAM,WAAW,mBAAmB;IAClC,0EAA0E;IAC1E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iCAAiC;IACjC,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,uBAAuB;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,UAAU,GAAE,UAAe,EAC3B,OAAO,GAAE,mBAAwB,GAChC,UAAU,CAgMZ"}