@revealui/core 0.5.5 → 0.6.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.
- package/README.md +30 -24
- package/dist/api/compression.d.ts +3 -4
- package/dist/api/compression.d.ts.map +1 -1
- package/dist/api/compression.js +1 -2
- package/dist/api/rate-limit.d.ts +11 -12
- package/dist/api/rate-limit.d.ts.map +1 -1
- package/dist/api/rate-limit.js +5 -6
- package/dist/api/response-cache.d.ts +8 -9
- package/dist/api/response-cache.d.ts.map +1 -1
- package/dist/api/response-cache.js +5 -4
- package/dist/api/rest.d.ts +1 -1
- package/dist/api/rest.js +2 -2
- package/dist/cache/query-cache.d.ts +1 -1
- package/dist/cache/query-cache.js +1 -1
- package/dist/caching/index.d.ts +1 -1
- package/dist/caching/index.js +1 -1
- package/dist/client/admin/components/AdminDashboard.d.ts.map +1 -1
- package/dist/client/admin/components/AdminDashboard.js +46 -3
- package/dist/client/admin/components/CollectionList.d.ts +3 -1
- package/dist/client/admin/components/CollectionList.d.ts.map +1 -1
- package/dist/client/admin/components/CollectionList.js +51 -2
- package/dist/client/admin/components/DocumentForm.js +2 -2
- package/dist/client/admin/layout.d.ts.map +1 -1
- package/dist/client/admin/layout.js +1 -3
- package/dist/client/admin/page.js +1 -1
- package/dist/client/admin/utils/apiClient.d.ts +17 -1
- package/dist/client/admin/utils/apiClient.d.ts.map +1 -1
- package/dist/client/admin/utils/apiClient.js +25 -1
- package/dist/client/hooks.d.ts +1 -1
- package/dist/client/hooks.js +1 -1
- package/dist/client/richtext/plugins/PastePlugin.d.ts.map +1 -1
- package/dist/client/richtext/plugins/PastePlugin.js +30 -0
- package/dist/client/ui/index.d.ts +2 -2
- package/dist/client/ui/index.js +2 -2
- package/dist/collections/operations/fieldHooks.d.ts +2 -2
- package/dist/collections/operations/fieldHooks.js +2 -2
- package/dist/collections/operations/update.js +1 -1
- package/dist/config/index.js +1 -1
- package/dist/config/runtime.d.ts +3 -3
- package/dist/config/runtime.d.ts.map +1 -1
- package/dist/config/runtime.js +2 -2
- package/dist/config/utils.d.ts.map +1 -1
- package/dist/config/utils.js +5 -0
- package/dist/database/safe-parse.d.ts +1 -1
- package/dist/database/safe-parse.js +3 -3
- package/dist/database/universal-postgres.d.ts +1 -1
- package/dist/database/universal-postgres.js +1 -1
- package/dist/error-handling/error-reporter.js +4 -4
- package/dist/features.d.ts +9 -9
- package/dist/features.d.ts.map +1 -1
- package/dist/features.js +3 -3
- package/dist/generated/index.d.ts +1 -1
- package/dist/generated/index.js +1 -1
- package/dist/generated/types/{cms.d.ts → admin.d.ts} +3 -3
- package/dist/generated/types/admin.d.ts.map +1 -0
- package/dist/generated/types/index.d.ts +2 -2
- package/dist/generated/types/index.d.ts.map +1 -1
- package/dist/generated/types/index.js +2 -2
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -10
- package/dist/instance/RevealUIInstance.js +2 -2
- package/dist/jobs/queue.d.ts +1 -1
- package/dist/jobs/queue.d.ts.map +1 -1
- package/dist/license-encryption.d.ts +11 -2
- package/dist/license-encryption.d.ts.map +1 -1
- package/dist/license-encryption.js +79 -24
- package/dist/license.d.ts +68 -5
- package/dist/license.d.ts.map +1 -1
- package/dist/license.js +171 -20
- package/dist/monitoring/zombie-detector.js +1 -1
- package/dist/nextjs/index.d.ts +0 -1
- package/dist/nextjs/index.d.ts.map +1 -1
- package/dist/nextjs/index.js +7 -2
- package/dist/nextjs/withRevealUI.d.ts +29 -1
- package/dist/nextjs/withRevealUI.d.ts.map +1 -1
- package/dist/observability/health-check.js +1 -1
- package/dist/observability/logger.d.ts +0 -4
- package/dist/observability/logger.d.ts.map +1 -1
- package/dist/observability/logger.js +2 -29
- package/dist/plugins/nested-docs.d.ts +1 -1
- package/dist/plugins/nested-docs.d.ts.map +1 -1
- package/dist/plugins/nested-docs.js +1 -1
- package/dist/relationships/analyzer.d.ts +1 -1
- package/dist/relationships/analyzer.js +2 -2
- package/dist/relationships/populate-core.d.ts +1 -1
- package/dist/relationships/populate-core.d.ts.map +1 -1
- package/dist/relationships/populate-core.js +5 -1
- package/dist/relationships/population.js +1 -1
- package/dist/revealui.d.ts +0 -5
- package/dist/revealui.d.ts.map +1 -1
- package/dist/revealui.js +0 -10
- package/dist/richtext/exports/client/rcc.js +1 -1
- package/dist/richtext/exports/server/rsc.d.ts +2 -17
- package/dist/richtext/exports/server/rsc.d.ts.map +1 -1
- package/dist/richtext/exports/server/rsc.js +9 -54
- package/dist/richtext/index.d.ts +1 -1
- package/dist/richtext/index.js +1 -1
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +1 -1
- package/dist/server/renderPage.js +1 -1
- package/dist/types/admin.d.ts +8 -0
- package/dist/types/admin.d.ts.map +1 -0
- package/dist/types/admin.js +6 -0
- package/dist/types/config.d.ts +2 -2
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/config.js +1 -1
- package/dist/types/extensions.d.ts +1 -1
- package/dist/types/extensions.d.ts.map +1 -1
- package/dist/types/generated.d.ts +4 -4
- package/dist/types/generated.d.ts.map +1 -1
- package/dist/types/generated.js +2 -2
- package/dist/types/hooks.d.ts +1 -1
- package/dist/types/hooks.d.ts.map +1 -1
- package/dist/types/index.d.ts +4 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +6 -6
- package/dist/types/jobs.d.ts +1 -1
- package/dist/types/jobs.js +1 -1
- package/dist/types/legacy.d.ts +1 -1
- package/dist/types/legacy.d.ts.map +1 -1
- package/dist/types/plugins.d.ts +1 -1
- package/dist/types/plugins.d.ts.map +1 -1
- package/dist/types/query.d.ts +2 -2
- package/dist/types/query.d.ts.map +1 -1
- package/dist/types/runtime.d.ts +2 -2
- package/dist/types/runtime.d.ts.map +1 -1
- package/dist/types/schema.d.ts +1 -1
- package/dist/types/schema.d.ts.map +1 -1
- package/dist/utils/api-wrapper.d.ts +4 -6
- package/dist/utils/api-wrapper.d.ts.map +1 -1
- package/dist/utils/api-wrapper.js +6 -9
- package/dist/utils/error-responses.js +1 -1
- package/dist/utils/field-conversion.js +1 -1
- package/dist/utils/type-guards.d.ts +1 -1
- package/dist/utils/type-guards.d.ts.map +1 -1
- package/package.json +87 -34
- package/dist/generated/types/cms.d.ts.map +0 -1
- package/dist/types/cms.d.ts +0 -8
- package/dist/types/cms.d.ts.map +0 -1
- package/dist/types/cms.js +0 -6
- /package/dist/generated/types/{cms.js → admin.js} +0 -0
package/dist/license.d.ts
CHANGED
|
@@ -1,18 +1,62 @@
|
|
|
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
|
|
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<{
|
|
14
|
-
max: "max";
|
|
15
58
|
pro: "pro";
|
|
59
|
+
max: "max";
|
|
16
60
|
enterprise: "enterprise";
|
|
17
61
|
}>;
|
|
18
62
|
customerId: z.ZodString;
|
|
@@ -26,7 +70,7 @@ declare const licensePayloadSchema: z.ZodObject<{
|
|
|
26
70
|
export type LicensePayload = z.infer<typeof licensePayloadSchema>;
|
|
27
71
|
/** License cache TTL configuration */
|
|
28
72
|
export interface LicenseCacheConfig {
|
|
29
|
-
/** Cache TTL in milliseconds (default:
|
|
73
|
+
/** Cache TTL in milliseconds (default: 15 seconds) */
|
|
30
74
|
ttlMs: number;
|
|
31
75
|
}
|
|
32
76
|
/**
|
|
@@ -37,8 +81,10 @@ export declare function configureLicenseCache(overrides: Partial<LicenseCacheCon
|
|
|
37
81
|
/**
|
|
38
82
|
* Computes a deterministic Key ID (kid) from a public key PEM string.
|
|
39
83
|
* Returns the first 8 characters of the SHA-256 hex digest of the PEM.
|
|
84
|
+
*
|
|
85
|
+
* Async because it uses `crypto.subtle.digest` for full edge compatibility.
|
|
40
86
|
*/
|
|
41
|
-
export declare function computeKeyId(publicKeyPem: string): string
|
|
87
|
+
export declare function computeKeyId(publicKeyPem: string): Promise<string>;
|
|
42
88
|
/**
|
|
43
89
|
* Validates a license key JWT and returns the decoded payload.
|
|
44
90
|
* Returns null if the key is invalid, expired, or missing.
|
|
@@ -63,8 +109,24 @@ export declare function getLicensePayload(): LicensePayload | null;
|
|
|
63
109
|
/**
|
|
64
110
|
* Checks whether the current license is at least the given tier.
|
|
65
111
|
* Also validates that the license has not expired (checks JWT exp claim).
|
|
112
|
+
*
|
|
113
|
+
* Subscription grace: if the JWT has expired but is within the configured
|
|
114
|
+
* grace period (default 3 days), access is still allowed. Use
|
|
115
|
+
* `getLicenseStatus()` to check whether the license is in grace.
|
|
66
116
|
*/
|
|
67
117
|
export declare function isLicensed(requiredTier: LicenseTier): boolean;
|
|
118
|
+
/**
|
|
119
|
+
* Returns the full license status including mode, grace state, and read-only flag.
|
|
120
|
+
*
|
|
121
|
+
* Use this for UI decisions (banners, warnings) and API response headers.
|
|
122
|
+
* For simple gate checks, `isLicensed()` is sufficient.
|
|
123
|
+
*/
|
|
124
|
+
export declare function getLicenseStatus(requiredTier?: LicenseTier): LicenseCheckResult;
|
|
125
|
+
/**
|
|
126
|
+
* Returns the configured grace period durations.
|
|
127
|
+
* Useful for API response headers and customer-facing documentation.
|
|
128
|
+
*/
|
|
129
|
+
export declare function getGraceConfig(): Readonly<GracePeriodConfig>;
|
|
68
130
|
/**
|
|
69
131
|
* Returns the maximum number of sites allowed by the current license.
|
|
70
132
|
*/
|
|
@@ -80,7 +142,8 @@ export declare function getMaxUsers(): number;
|
|
|
80
142
|
export declare function getMaxAgentTasks(): number;
|
|
81
143
|
/**
|
|
82
144
|
* Generates a signed license key JWT.
|
|
83
|
-
*
|
|
145
|
+
* Server-only in practice (requires the private key) but edge-compatible —
|
|
146
|
+
* `jose.importPKCS8` and `SignJWT` both run on Web Crypto.
|
|
84
147
|
*
|
|
85
148
|
* @param payload - License payload (tier, customerId, limits, perpetual flag)
|
|
86
149
|
* @param privateKey - RS256 private key (PEM format)
|
package/dist/license.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"license.d.ts","sourceRoot":"","sources":["../src/license.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"license.d.ts","sourceRoot":"","sources":["../src/license.ts"],"names":[],"mappings":"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"}
|
package/dist/license.js
CHANGED
|
@@ -1,15 +1,42 @@
|
|
|
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
|
|
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
12
|
import { decodeProtectedHeader, importPKCS8, importSPKI, jwtVerify, SignJWT } from 'jose';
|
|
10
13
|
import { z } from 'zod';
|
|
11
14
|
import { decryptLicenseKey } from './license-encryption.js';
|
|
12
15
|
import { logger } from './utils/logger.js';
|
|
16
|
+
/** JWT issuer and audience for license tokens — prevents cross-environment replay */
|
|
17
|
+
const LICENSE_ISSUER = process.env.REVEALUI_LICENSE_ISSUER ?? 'https://revealui.com';
|
|
18
|
+
const LICENSE_AUDIENCE = process.env.REVEALUI_LICENSE_AUDIENCE ?? 'revealui-license';
|
|
19
|
+
const DEFAULT_GRACE = {
|
|
20
|
+
subscriptionDays: parseEnvInt('LICENSE_GRACE_SUBSCRIPTION_DAYS', 3),
|
|
21
|
+
perpetualDays: parseEnvInt('LICENSE_GRACE_PERPETUAL_DAYS', 30),
|
|
22
|
+
infraDays: parseEnvInt('LICENSE_GRACE_INFRA_DAYS', 7),
|
|
23
|
+
};
|
|
24
|
+
function parseEnvInt(key, fallback) {
|
|
25
|
+
const val = process.env[key];
|
|
26
|
+
if (val) {
|
|
27
|
+
const parsed = Number.parseInt(val, 10);
|
|
28
|
+
if (Number.isFinite(parsed) && parsed >= 0)
|
|
29
|
+
return parsed;
|
|
30
|
+
}
|
|
31
|
+
return fallback;
|
|
32
|
+
}
|
|
33
|
+
let graceConfig = { ...DEFAULT_GRACE };
|
|
34
|
+
/**
|
|
35
|
+
* Configure grace period durations. Useful for testing.
|
|
36
|
+
*/
|
|
37
|
+
export function configureGracePeriods(overrides) {
|
|
38
|
+
graceConfig = { ...DEFAULT_GRACE, ...overrides };
|
|
39
|
+
}
|
|
13
40
|
/** Decoded license payload schema */
|
|
14
41
|
const licensePayloadSchema = z.object({
|
|
15
42
|
/** License tier */
|
|
@@ -25,16 +52,25 @@ const licensePayloadSchema = z.object({
|
|
|
25
52
|
/**
|
|
26
53
|
* True for one-time perpetual purchases.
|
|
27
54
|
* When set, the exp claim is omitted from the JWT and isLicensed() skips
|
|
28
|
-
* expiry checks
|
|
55
|
+
* expiry checks - the license is valid as long as it hasn't been revoked.
|
|
29
56
|
*/
|
|
30
57
|
perpetual: z.boolean().optional(),
|
|
31
58
|
/** License issued-at timestamp */
|
|
32
59
|
iat: z.number().optional(),
|
|
33
|
-
/** License expiration timestamp
|
|
60
|
+
/** License expiration timestamp - absent for perpetual licenses */
|
|
34
61
|
exp: z.number().optional(),
|
|
35
62
|
});
|
|
63
|
+
const DEFAULT_TTL_MS = 15_000; // 15 seconds - revoked licenses lose access quickly
|
|
36
64
|
const DEFAULT_CACHE_CONFIG = {
|
|
37
|
-
ttlMs:
|
|
65
|
+
ttlMs: (() => {
|
|
66
|
+
const envTtl = process.env.LICENSE_CACHE_TTL_MS;
|
|
67
|
+
if (envTtl) {
|
|
68
|
+
const parsed = Number.parseInt(envTtl, 10);
|
|
69
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
70
|
+
return parsed;
|
|
71
|
+
}
|
|
72
|
+
return DEFAULT_TTL_MS;
|
|
73
|
+
})(),
|
|
38
74
|
};
|
|
39
75
|
let cacheConfig = { ...DEFAULT_CACHE_CONFIG };
|
|
40
76
|
let cachedAt = 0;
|
|
@@ -49,6 +85,7 @@ let cachedState = {
|
|
|
49
85
|
tier: 'free',
|
|
50
86
|
payload: null,
|
|
51
87
|
validatedAt: null,
|
|
88
|
+
keyPresentButInvalid: false,
|
|
52
89
|
};
|
|
53
90
|
/**
|
|
54
91
|
* The public key used to verify license JWTs.
|
|
@@ -57,14 +94,14 @@ let cachedState = {
|
|
|
57
94
|
*/
|
|
58
95
|
function getPublicKey() {
|
|
59
96
|
const raw = process.env.REVEALUI_LICENSE_PUBLIC_KEY ?? null;
|
|
60
|
-
// Docker/env files store PEM as single-line with literal \n
|
|
97
|
+
// Docker/env files store PEM as single-line with literal \n - restore real newlines
|
|
61
98
|
return raw ? raw.replace(/\\n/g, '\n') : null;
|
|
62
99
|
}
|
|
63
100
|
/**
|
|
64
101
|
* Reads the license key from environment.
|
|
65
102
|
* Supports encrypted keys (enc:iv:ciphertext:tag format) via REVEALUI_LICENSE_ENCRYPTION_KEY.
|
|
66
103
|
*/
|
|
67
|
-
function getLicenseKey() {
|
|
104
|
+
async function getLicenseKey() {
|
|
68
105
|
const raw = process.env.REVEALUI_LICENSE_KEY ?? null;
|
|
69
106
|
if (!raw)
|
|
70
107
|
return null;
|
|
@@ -73,9 +110,18 @@ function getLicenseKey() {
|
|
|
73
110
|
/**
|
|
74
111
|
* Computes a deterministic Key ID (kid) from a public key PEM string.
|
|
75
112
|
* Returns the first 8 characters of the SHA-256 hex digest of the PEM.
|
|
113
|
+
*
|
|
114
|
+
* Async because it uses `crypto.subtle.digest` for full edge compatibility.
|
|
76
115
|
*/
|
|
77
|
-
export function computeKeyId(publicKeyPem) {
|
|
78
|
-
|
|
116
|
+
export async function computeKeyId(publicKeyPem) {
|
|
117
|
+
const encoded = new TextEncoder().encode(publicKeyPem);
|
|
118
|
+
const digest = new Uint8Array(await crypto.subtle.digest('SHA-256', encoded));
|
|
119
|
+
let hex = '';
|
|
120
|
+
// Only the first 4 bytes (8 hex chars) — enough to identify rotated keys.
|
|
121
|
+
for (const b of digest.subarray(0, 4)) {
|
|
122
|
+
hex += b.toString(16).padStart(2, '0');
|
|
123
|
+
}
|
|
124
|
+
return hex;
|
|
79
125
|
}
|
|
80
126
|
/**
|
|
81
127
|
* Validates a license key JWT and returns the decoded payload.
|
|
@@ -85,14 +131,19 @@ export async function validateLicenseKey(licenseKey, publicKey) {
|
|
|
85
131
|
try {
|
|
86
132
|
// Extract kid from JWT header for forward-compatible key rotation
|
|
87
133
|
const header = decodeProtectedHeader(licenseKey);
|
|
88
|
-
const expectedKid = computeKeyId(publicKey);
|
|
134
|
+
const expectedKid = await computeKeyId(publicKey);
|
|
89
135
|
if (header.kid && header.kid !== expectedKid) {
|
|
90
136
|
logger.warn(`JWT kid mismatch: token has "${header.kid}", current key is "${expectedKid}". ` +
|
|
91
137
|
'Token may have been signed with a rotated key.');
|
|
92
138
|
}
|
|
93
139
|
const key = await importSPKI(publicKey, 'RS256');
|
|
140
|
+
// Accept tokens expired within the subscription grace window so the
|
|
141
|
+
// payload is available for grace-period calculations in isLicensed().
|
|
94
142
|
const { payload } = await jwtVerify(licenseKey, key, {
|
|
95
|
-
algorithms: ['RS256'
|
|
143
|
+
algorithms: ['RS256'],
|
|
144
|
+
clockTolerance: graceConfig.subscriptionDays * 86_400,
|
|
145
|
+
issuer: LICENSE_ISSUER,
|
|
146
|
+
audience: LICENSE_AUDIENCE,
|
|
96
147
|
});
|
|
97
148
|
const result = licensePayloadSchema.safeParse(payload);
|
|
98
149
|
if (!result.success) {
|
|
@@ -111,16 +162,27 @@ export async function validateLicenseKey(licenseKey, publicKey) {
|
|
|
111
162
|
* @returns The resolved license tier
|
|
112
163
|
*/
|
|
113
164
|
export async function initializeLicense() {
|
|
114
|
-
const licenseKey = getLicenseKey();
|
|
165
|
+
const licenseKey = await getLicenseKey();
|
|
115
166
|
const publicKey = getPublicKey();
|
|
116
167
|
if (!(licenseKey && publicKey)) {
|
|
117
|
-
cachedState = {
|
|
168
|
+
cachedState = {
|
|
169
|
+
tier: 'free',
|
|
170
|
+
payload: null,
|
|
171
|
+
validatedAt: Date.now(),
|
|
172
|
+
keyPresentButInvalid: false,
|
|
173
|
+
};
|
|
118
174
|
cachedAt = Date.now();
|
|
119
175
|
return 'free';
|
|
120
176
|
}
|
|
121
177
|
const payload = await validateLicenseKey(licenseKey, publicKey);
|
|
122
178
|
if (!payload) {
|
|
123
|
-
|
|
179
|
+
// Key was present but failed validation (expired beyond grace, invalid signature, etc.)
|
|
180
|
+
cachedState = {
|
|
181
|
+
tier: 'free',
|
|
182
|
+
payload: null,
|
|
183
|
+
validatedAt: Date.now(),
|
|
184
|
+
keyPresentButInvalid: true,
|
|
185
|
+
};
|
|
124
186
|
cachedAt = Date.now();
|
|
125
187
|
return 'free';
|
|
126
188
|
}
|
|
@@ -128,6 +190,7 @@ export async function initializeLicense() {
|
|
|
128
190
|
tier: payload.tier,
|
|
129
191
|
payload,
|
|
130
192
|
validatedAt: Date.now(),
|
|
193
|
+
keyPresentButInvalid: false,
|
|
131
194
|
};
|
|
132
195
|
cachedAt = Date.now();
|
|
133
196
|
// Clamp cache TTL to license expiry so revoked licenses don't survive the full TTL
|
|
@@ -145,7 +208,7 @@ export async function initializeLicense() {
|
|
|
145
208
|
*/
|
|
146
209
|
function evictStaleCache() {
|
|
147
210
|
if (cachedAt > 0 && Date.now() - cachedAt > cacheConfig.ttlMs) {
|
|
148
|
-
cachedState = { tier: 'free', payload: null, validatedAt: null };
|
|
211
|
+
cachedState = { tier: 'free', payload: null, validatedAt: null, keyPresentButInvalid: false };
|
|
149
212
|
cachedAt = 0;
|
|
150
213
|
}
|
|
151
214
|
}
|
|
@@ -167,6 +230,10 @@ export function getLicensePayload() {
|
|
|
167
230
|
/**
|
|
168
231
|
* Checks whether the current license is at least the given tier.
|
|
169
232
|
* Also validates that the license has not expired (checks JWT exp claim).
|
|
233
|
+
*
|
|
234
|
+
* Subscription grace: if the JWT has expired but is within the configured
|
|
235
|
+
* grace period (default 3 days), access is still allowed. Use
|
|
236
|
+
* `getLicenseStatus()` to check whether the license is in grace.
|
|
170
237
|
*/
|
|
171
238
|
export function isLicensed(requiredTier) {
|
|
172
239
|
evictStaleCache();
|
|
@@ -179,15 +246,94 @@ export function isLicensed(requiredTier) {
|
|
|
179
246
|
// Free tier is always available
|
|
180
247
|
if (requiredTier === 'free')
|
|
181
248
|
return true;
|
|
182
|
-
// Perpetual licenses never expire
|
|
249
|
+
// Perpetual licenses never expire - skip the exp check entirely
|
|
183
250
|
if (!cachedState.payload?.perpetual && cachedState.payload?.exp) {
|
|
184
251
|
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
185
252
|
if (cachedState.payload.exp < nowSeconds) {
|
|
253
|
+
// Expired — check subscription grace period
|
|
254
|
+
const graceEndSeconds = cachedState.payload.exp + graceConfig.subscriptionDays * 86_400;
|
|
255
|
+
if (nowSeconds < graceEndSeconds) {
|
|
256
|
+
// Within grace — still allowed, but callers should check getLicenseStatus()
|
|
257
|
+
return tierRank[cachedState.tier] >= tierRank[requiredTier];
|
|
258
|
+
}
|
|
186
259
|
return false;
|
|
187
260
|
}
|
|
188
261
|
}
|
|
189
262
|
return tierRank[cachedState.tier] >= tierRank[requiredTier];
|
|
190
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* Returns the full license status including mode, grace state, and read-only flag.
|
|
266
|
+
*
|
|
267
|
+
* Use this for UI decisions (banners, warnings) and API response headers.
|
|
268
|
+
* For simple gate checks, `isLicensed()` is sufficient.
|
|
269
|
+
*/
|
|
270
|
+
export function getLicenseStatus(requiredTier = 'pro') {
|
|
271
|
+
evictStaleCache();
|
|
272
|
+
const tierRank = {
|
|
273
|
+
free: 0,
|
|
274
|
+
pro: 1,
|
|
275
|
+
max: 2,
|
|
276
|
+
enterprise: 3,
|
|
277
|
+
};
|
|
278
|
+
// No license configured — or key was present but failed validation
|
|
279
|
+
if (!cachedState.payload) {
|
|
280
|
+
if (cachedState.keyPresentButInvalid) {
|
|
281
|
+
return {
|
|
282
|
+
allowed: requiredTier === 'free',
|
|
283
|
+
tier: 'free',
|
|
284
|
+
mode: 'expired',
|
|
285
|
+
reason: 'License key failed validation (expired beyond grace or invalid)',
|
|
286
|
+
readOnly: false,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
allowed: requiredTier === 'free',
|
|
291
|
+
tier: 'free',
|
|
292
|
+
mode: 'missing',
|
|
293
|
+
reason: 'No license configured',
|
|
294
|
+
readOnly: false,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
const nowSeconds = Math.floor(Date.now() / 1000);
|
|
298
|
+
// Check subscription expiry + grace
|
|
299
|
+
if (!cachedState.payload.perpetual && cachedState.payload.exp) {
|
|
300
|
+
if (cachedState.payload.exp < nowSeconds) {
|
|
301
|
+
const graceEndSeconds = cachedState.payload.exp + graceConfig.subscriptionDays * 86_400;
|
|
302
|
+
if (nowSeconds < graceEndSeconds) {
|
|
303
|
+
const graceRemainingMs = (graceEndSeconds - nowSeconds) * 1000;
|
|
304
|
+
return {
|
|
305
|
+
allowed: tierRank[cachedState.tier] >= tierRank[requiredTier],
|
|
306
|
+
tier: cachedState.tier,
|
|
307
|
+
mode: 'grace',
|
|
308
|
+
reason: `Subscription expired, ${Math.ceil(graceRemainingMs / 86_400_000)}-day grace remaining`,
|
|
309
|
+
graceRemainingMs,
|
|
310
|
+
readOnly: false,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
return {
|
|
314
|
+
allowed: requiredTier === 'free',
|
|
315
|
+
tier: 'free',
|
|
316
|
+
mode: 'expired',
|
|
317
|
+
reason: 'Subscription expired and grace period exhausted',
|
|
318
|
+
readOnly: false,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// Active license
|
|
323
|
+
return {
|
|
324
|
+
allowed: tierRank[cachedState.tier] >= tierRank[requiredTier],
|
|
325
|
+
tier: cachedState.tier,
|
|
326
|
+
mode: 'active',
|
|
327
|
+
readOnly: false,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Returns the configured grace period durations.
|
|
332
|
+
* Useful for API response headers and customer-facing documentation.
|
|
333
|
+
*/
|
|
334
|
+
export function getGraceConfig() {
|
|
335
|
+
return graceConfig;
|
|
336
|
+
}
|
|
191
337
|
/**
|
|
192
338
|
* Returns the maximum number of sites allowed by the current license.
|
|
193
339
|
*/
|
|
@@ -230,7 +376,8 @@ export function getMaxAgentTasks() {
|
|
|
230
376
|
}
|
|
231
377
|
/**
|
|
232
378
|
* Generates a signed license key JWT.
|
|
233
|
-
*
|
|
379
|
+
* Server-only in practice (requires the private key) but edge-compatible —
|
|
380
|
+
* `jose.importPKCS8` and `SignJWT` both run on Web Crypto.
|
|
234
381
|
*
|
|
235
382
|
* @param payload - License payload (tier, customerId, limits, perpetual flag)
|
|
236
383
|
* @param privateKey - RS256 private key (PEM format)
|
|
@@ -242,12 +389,16 @@ export function getMaxAgentTasks() {
|
|
|
242
389
|
*/
|
|
243
390
|
export async function generateLicenseKey(payload, privateKey, expiresInSeconds = 365 * 24 * 60 * 60, publicKey) {
|
|
244
391
|
const key = await importPKCS8(privateKey, 'RS256');
|
|
245
|
-
const kid = publicKey ? computeKeyId(publicKey) : undefined;
|
|
392
|
+
const kid = publicKey ? await computeKeyId(publicKey) : undefined;
|
|
246
393
|
const header = { alg: 'RS256' };
|
|
247
394
|
if (kid) {
|
|
248
395
|
header.kid = kid;
|
|
249
396
|
}
|
|
250
|
-
const builder = new SignJWT({ ...payload })
|
|
397
|
+
const builder = new SignJWT({ ...payload })
|
|
398
|
+
.setProtectedHeader(header)
|
|
399
|
+
.setIssuedAt()
|
|
400
|
+
.setIssuer(LICENSE_ISSUER)
|
|
401
|
+
.setAudience(LICENSE_AUDIENCE);
|
|
251
402
|
if (expiresInSeconds !== null) {
|
|
252
403
|
builder.setExpirationTime(`${expiresInSeconds}s`);
|
|
253
404
|
}
|
|
@@ -257,6 +408,6 @@ export async function generateLicenseKey(payload, privateKey, expiresInSeconds =
|
|
|
257
408
|
* Reset license state. Primarily for testing.
|
|
258
409
|
*/
|
|
259
410
|
export function resetLicenseState() {
|
|
260
|
-
cachedState = { tier: 'free', payload: null, validatedAt: null };
|
|
411
|
+
cachedState = { tier: 'free', payload: null, validatedAt: null, keyPresentButInvalid: false };
|
|
261
412
|
cachedAt = 0;
|
|
262
413
|
}
|
|
@@ -28,7 +28,7 @@ class ZombieDetector {
|
|
|
28
28
|
return;
|
|
29
29
|
// Skip on serverless environments where ps is unavailable or meaningless
|
|
30
30
|
if (process.env.VERCEL === '1' || process.env.AWS_LAMBDA_FUNCTION_NAME) {
|
|
31
|
-
logger.debug('Zombie detection skipped
|
|
31
|
+
logger.debug('Zombie detection skipped - serverless environment detected');
|
|
32
32
|
return;
|
|
33
33
|
}
|
|
34
34
|
logger.info('Starting zombie process detector', {
|
package/dist/nextjs/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/nextjs/index.ts"],"names":[],"mappings":"
|
|
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"}
|
package/dist/nextjs/index.js
CHANGED
|
@@ -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
|
-
|
|
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":"
|
|
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"}
|
|
@@ -115,7 +115,7 @@ export function createDatabaseHealthCheck(queryFn) {
|
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
117
|
catch (error) {
|
|
118
|
-
// Surface the root cause
|
|
118
|
+
// Surface the root cause - Drizzle wraps Neon errors with "Failed query: ..."
|
|
119
119
|
// but the actual HTTP/connection error is in .cause
|
|
120
120
|
let message = error instanceof Error ? error.message : 'Database connection failed';
|
|
121
121
|
if (error instanceof Error && error.cause instanceof Error) {
|
|
@@ -40,8 +40,4 @@ export declare function logUserAction(action: string, userId?: string, context?:
|
|
|
40
40
|
* System event logger
|
|
41
41
|
*/
|
|
42
42
|
export declare function logSystemEvent(event: string, context?: Record<string, unknown>): void;
|
|
43
|
-
/**
|
|
44
|
-
* Sanitize sensitive data from logs
|
|
45
|
-
*/
|
|
46
|
-
export declare function sanitizeLogData(data: Record<string, unknown>): Record<string, unknown>;
|
|
47
43
|
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/observability/logger.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,YAAY,EACV,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,QAAQ,GACT,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,YAAY,EACZ,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,QAAQ,GACT,MAAM,wBAAwB,CAAC;AAKhC;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,GAAG,OAAO,EAAE,SAAS,GAAG,OAAO,EACzE,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,IAG/D,SAAS,QAAQ,GAAG;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE;QACR,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;QACrC,OAAO,CAAC,EAAE,MAAM,QAAQ,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;KAC5C,CAAC;CACH,EACD,MAAM,MAAM,OAAO,CAAC,SAAS,CAAC,KAC7B,OAAO,CAAC,SAAS,CAAC,CA6CtB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI,CASN;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI,CAgBN;AAED;;GAEG;AACH,wBAAgB,QAAQ,CACtB,SAAS,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,EAC5C,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI,CAMN;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI,CAMN;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAKrF
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/observability/logger.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,YAAY,EACV,UAAU,EACV,QAAQ,EACR,YAAY,EACZ,QAAQ,GACT,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,YAAY,EACZ,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,QAAQ,GACT,MAAM,wBAAwB,CAAC;AAKhC;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,GAAG,OAAO,EAAE,SAAS,GAAG,OAAO,EACzE,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,IAG/D,SAAS,QAAQ,GAAG;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE;QACR,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;QACrC,OAAO,CAAC,EAAE,MAAM,QAAQ,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;KAC5C,CAAC;CACH,EACD,MAAM,MAAM,OAAO,CAAC,SAAS,CAAC,KAC7B,OAAO,CAAC,SAAS,CAAC,CA6CtB;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI,CASN;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI,CAgBN;AAED;;GAEG;AACH,wBAAgB,QAAQ,CACtB,SAAS,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,EAC5C,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI,CAMN;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAChC,IAAI,CAMN;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAKrF"}
|
|
@@ -109,32 +109,5 @@ export function logSystemEvent(event, context) {
|
|
|
109
109
|
event,
|
|
110
110
|
});
|
|
111
111
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
*/
|
|
115
|
-
export function sanitizeLogData(data) {
|
|
116
|
-
const sensitiveKeys = [
|
|
117
|
-
'password',
|
|
118
|
-
'token',
|
|
119
|
-
'secret',
|
|
120
|
-
'apiKey',
|
|
121
|
-
'accessToken',
|
|
122
|
-
'refreshToken',
|
|
123
|
-
'creditCard',
|
|
124
|
-
'ssn',
|
|
125
|
-
];
|
|
126
|
-
const sanitized = {};
|
|
127
|
-
for (const [key, value] of Object.entries(data)) {
|
|
128
|
-
const lowerKey = key.toLowerCase();
|
|
129
|
-
if (sensitiveKeys.some((sensitive) => lowerKey.includes(sensitive.toLowerCase()))) {
|
|
130
|
-
sanitized[key] = '[REDACTED]';
|
|
131
|
-
}
|
|
132
|
-
else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
133
|
-
sanitized[key] = sanitizeLogData(value);
|
|
134
|
-
}
|
|
135
|
-
else {
|
|
136
|
-
sanitized[key] = value;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return sanitized;
|
|
140
|
-
}
|
|
112
|
+
// Log redaction lives in @revealui/security — import `redactLogContext`
|
|
113
|
+
// (recursive walker) or `redactLogField` (single key/value) from there.
|
|
@@ -3,7 +3,7 @@ export interface NestedDocsPluginConfig {
|
|
|
3
3
|
collections?: string[];
|
|
4
4
|
parentFieldSlug?: string;
|
|
5
5
|
breadcrumbsFieldSlug?: string;
|
|
6
|
-
/** Drizzle DB client getter
|
|
6
|
+
/** Drizzle DB client getter - if not provided, breadcrumbs will be empty */
|
|
7
7
|
getDb?: () => unknown;
|
|
8
8
|
/** Label field to use for breadcrumb labels (defaults to 'title') */
|
|
9
9
|
labelField?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nested-docs.d.ts","sourceRoot":"","sources":["../../src/plugins/nested-docs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,WAAW,sBAAsB;IACrC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,
|
|
1
|
+
{"version":3,"file":"nested-docs.d.ts","sourceRoot":"","sources":["../../src/plugins/nested-docs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,WAAW,sBAAsB;IACrC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC;IACtB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAgED,wBAAgB,gBAAgB,CAAC,MAAM,GAAE,sBAA2B,GAAG,MAAM,CA2F5E"}
|
|
@@ -16,7 +16,7 @@ async function buildBreadcrumbs(db, collectionSlug, parentId, parentFieldSlug, l
|
|
|
16
16
|
}
|
|
17
17
|
while (currentId && depth < maxDepth) {
|
|
18
18
|
try {
|
|
19
|
-
// Use parameterized query
|
|
19
|
+
// Use parameterized query - $1 is the only user-controlled value
|
|
20
20
|
const result = await db.execute({
|
|
21
21
|
sql: `SELECT id, "${labelField}", "${parentFieldSlug}" FROM "${collectionSlug}" WHERE id = $1 LIMIT 1`,
|
|
22
22
|
params: [currentId],
|
|
@@ -6,7 +6,7 @@ import type { RelationshipMetadata } from '../types/query.js';
|
|
|
6
6
|
* Analyzes a collection config and extracts all relationship fields with their metadata.
|
|
7
7
|
* This is the foundation for depth-based relationship population.
|
|
8
8
|
*
|
|
9
|
-
* Based on RevealUI
|
|
9
|
+
* Based on RevealUI admin analysis:
|
|
10
10
|
* - Simple relationships (single, no hasMany): Direct Foreign Keys
|
|
11
11
|
* - hasMany relationships: Junction Tables
|
|
12
12
|
* - Polymorphic relationships (relationTo array): Junction Tables with multiple FK columns
|
|
@@ -58,7 +58,7 @@ export function getRelationshipFields(config, collectionSlug) {
|
|
|
58
58
|
}
|
|
59
59
|
/**
|
|
60
60
|
* Creates relationship metadata for a single field.
|
|
61
|
-
* Determines storage type based on field properties following RevealUI
|
|
61
|
+
* Determines storage type based on field properties following RevealUI admin patterns.
|
|
62
62
|
*/
|
|
63
63
|
function createRelationshipMetadata(field, fieldPath, parentTableName, isLocalized = false) {
|
|
64
64
|
// Skip if not a relationship field
|
|
@@ -72,7 +72,7 @@ function createRelationshipMetadata(field, fieldPath, parentTableName, isLocaliz
|
|
|
72
72
|
const relationTo = field.relationTo;
|
|
73
73
|
const hasMany = field.hasMany ?? false;
|
|
74
74
|
const isPolymorphic = Array.isArray(relationTo);
|
|
75
|
-
// Determine storage type based on RevealUI
|
|
75
|
+
// Determine storage type based on RevealUI admin analysis
|
|
76
76
|
let storageType;
|
|
77
77
|
if (isPolymorphic) {
|
|
78
78
|
storageType = 'polymorphic';
|