@revealui/core 0.6.0 → 0.8.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 +2 -2
- package/dist/api/rate-limit.d.ts.map +1 -1
- package/dist/api/rate-limit.js +8 -14
- package/dist/client/admin/utils/apiClient.d.ts.map +1 -1
- package/dist/client/admin/utils/apiClient.js +16 -1
- package/dist/client/richtext/index.d.ts.map +1 -1
- package/dist/client/richtext/index.js +0 -1
- package/dist/collections/operations/create.d.ts.map +1 -1
- package/dist/collections/operations/create.js +23 -5
- package/dist/collections/operations/delete.d.ts +2 -4
- package/dist/collections/operations/delete.d.ts.map +1 -1
- package/dist/collections/operations/delete.js +21 -4
- package/dist/collections/operations/findById.d.ts.map +1 -1
- package/dist/collections/operations/findById.js +18 -3
- package/dist/collections/operations/update.d.ts.map +1 -1
- package/dist/collections/operations/update.js +21 -4
- package/dist/database/universal-postgres.d.ts +14 -0
- package/dist/database/universal-postgres.d.ts.map +1 -1
- package/dist/database/universal-postgres.js +97 -0
- package/dist/fieldTraversal.d.ts +1 -1
- package/dist/fieldTraversal.d.ts.map +1 -1
- package/dist/instance/methods/create.d.ts.map +1 -1
- package/dist/instance/methods/create.js +3 -2
- package/dist/license.d.ts +40 -5
- package/dist/license.d.ts.map +1 -1
- package/dist/license.js +88 -22
- package/dist/observability/metrics.d.ts +15 -0
- package/dist/observability/metrics.d.ts.map +1 -1
- package/dist/observability/metrics.js +21 -0
- package/dist/revforge-license.d.ts +60 -0
- package/dist/revforge-license.d.ts.map +1 -0
- package/dist/revforge-license.js +74 -0
- package/dist/richtext/exports/server/rsc.d.ts +1 -1
- package/dist/richtext/exports/server/rsc.d.ts.map +1 -1
- package/dist/richtext/exports/server/rsc.js +7 -2
- package/dist/security/index.d.ts +9 -2
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +9 -2
- package/dist/storage/_helpers.d.ts +14 -0
- package/dist/storage/_helpers.d.ts.map +1 -0
- package/dist/storage/_helpers.js +40 -0
- package/dist/storage/index.d.ts +16 -0
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +43 -1
- package/dist/storage/mock.d.ts +39 -0
- package/dist/storage/mock.d.ts.map +1 -0
- package/dist/storage/mock.js +91 -0
- package/dist/storage/r2.d.ts +13 -0
- package/dist/storage/r2.d.ts.map +1 -0
- package/dist/storage/r2.js +117 -0
- package/dist/storage/types.d.ts +110 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +16 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/{legacy.d.ts → internal.d.ts} +1 -1
- package/dist/types/internal.d.ts.map +1 -0
- package/dist/types/runtime.d.ts +12 -0
- package/dist/types/runtime.d.ts.map +1 -1
- package/dist/utils/api-wrapper.d.ts.map +1 -1
- package/dist/utils/api-wrapper.js +2 -1
- package/package.json +33 -24
- package/dist/types/legacy.d.ts.map +0 -1
- /package/dist/types/{legacy.js → internal.js} +0 -0
package/dist/license.d.ts
CHANGED
|
@@ -55,11 +55,12 @@ export declare function configureGracePeriods(overrides: Partial<GracePeriodConf
|
|
|
55
55
|
/** Decoded license payload schema */
|
|
56
56
|
declare const licensePayloadSchema: z.ZodObject<{
|
|
57
57
|
tier: z.ZodEnum<{
|
|
58
|
-
pro: "pro";
|
|
59
58
|
max: "max";
|
|
59
|
+
pro: "pro";
|
|
60
60
|
enterprise: "enterprise";
|
|
61
61
|
}>;
|
|
62
62
|
customerId: z.ZodString;
|
|
63
|
+
jti: z.ZodString;
|
|
63
64
|
domains: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
64
65
|
maxSites: z.ZodOptional<z.ZodNumber>;
|
|
65
66
|
maxUsers: z.ZodOptional<z.ZodNumber>;
|
|
@@ -73,6 +74,27 @@ export interface LicenseCacheConfig {
|
|
|
73
74
|
/** Cache TTL in milliseconds (default: 15 seconds) */
|
|
74
75
|
ttlMs: number;
|
|
75
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;
|
|
76
98
|
/**
|
|
77
99
|
* Configure the license cache TTL.
|
|
78
100
|
* Useful for tests (short TTL) or deployments needing faster revocation detection.
|
|
@@ -88,8 +110,19 @@ export declare function computeKeyId(publicKeyPem: string): Promise<string>;
|
|
|
88
110
|
/**
|
|
89
111
|
* Validates a license key JWT and returns the decoded payload.
|
|
90
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.
|
|
91
124
|
*/
|
|
92
|
-
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>;
|
|
93
126
|
/**
|
|
94
127
|
* Initialize the license system. Call once at application startup.
|
|
95
128
|
* Reads REVEALUI_LICENSE_KEY and REVEALUI_LICENSE_PUBLIC_KEY from environment.
|
|
@@ -146,14 +179,16 @@ export declare function getMaxAgentTasks(): number;
|
|
|
146
179
|
* `jose.importPKCS8` and `SignJWT` both run on Web Crypto.
|
|
147
180
|
*
|
|
148
181
|
* @param payload - License payload (tier, customerId, limits, perpetual flag)
|
|
149
|
-
* @param privateKey -
|
|
182
|
+
* @param privateKey - Ed25519 private key (PEM format)
|
|
150
183
|
* @param expiresInSeconds - JWT expiration in seconds. Pass null for perpetual
|
|
151
184
|
* licenses (no exp claim). Defaults to 1 year for subscription licenses.
|
|
152
|
-
* @param publicKey -
|
|
185
|
+
* @param publicKey - Ed25519 public key (PEM format). When provided, a `kid`
|
|
153
186
|
* claim is added to the JWT header for forward-compatible key rotation.
|
|
154
187
|
* @returns Signed JWT string
|
|
155
188
|
*/
|
|
156
|
-
export declare function generateLicenseKey(payload: Omit<LicensePayload, 'iat' | 'exp'
|
|
189
|
+
export declare function generateLicenseKey(payload: Omit<LicensePayload, 'iat' | 'exp' | 'jti'> & {
|
|
190
|
+
jti?: string;
|
|
191
|
+
}, privateKey: string, expiresInSeconds?: number | null, publicKey?: string): Promise<string>;
|
|
157
192
|
/**
|
|
158
193
|
* Reset license state. Primarily for testing.
|
|
159
194
|
*/
|
package/dist/license.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"license.d.ts","sourceRoot":"","sources":["../src/license.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;
|
|
1
|
+
{"version":3,"file":"license.d.ts","sourceRoot":"","sources":["../src/license.ts"],"names":[],"mappings":"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"}
|
package/dist/license.js
CHANGED
|
@@ -9,10 +9,16 @@
|
|
|
9
9
|
* - jose - JWT signing/verification (Web Crypto API)
|
|
10
10
|
* - zod - Schema validation for license payloads
|
|
11
11
|
*/
|
|
12
|
-
|
|
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.
|
|
13
16
|
import { z } from 'zod';
|
|
14
17
|
import { decryptLicenseKey } from './license-encryption.js';
|
|
15
18
|
import { logger } from './utils/logger.js';
|
|
19
|
+
async function getJose() {
|
|
20
|
+
return await import('jose');
|
|
21
|
+
}
|
|
16
22
|
/** JWT issuer and audience for license tokens — prevents cross-environment replay */
|
|
17
23
|
const LICENSE_ISSUER = process.env.REVEALUI_LICENSE_ISSUER ?? 'https://revealui.com';
|
|
18
24
|
const LICENSE_AUDIENCE = process.env.REVEALUI_LICENSE_AUDIENCE ?? 'revealui-license';
|
|
@@ -41,8 +47,10 @@ export function configureGracePeriods(overrides) {
|
|
|
41
47
|
const licensePayloadSchema = z.object({
|
|
42
48
|
/** License tier */
|
|
43
49
|
tier: z.enum(['pro', 'max', 'enterprise']),
|
|
44
|
-
/** Organization or customer ID */
|
|
45
|
-
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),
|
|
46
54
|
/** Licensed domain(s) */
|
|
47
55
|
domains: z.array(z.string()).optional(),
|
|
48
56
|
/** Maximum number of sites allowed */
|
|
@@ -61,16 +69,40 @@ const licensePayloadSchema = z.object({
|
|
|
61
69
|
exp: z.number().optional(),
|
|
62
70
|
});
|
|
63
71
|
const DEFAULT_TTL_MS = 15_000; // 15 seconds - revoked licenses lose access quickly
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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)
|
|
72
97
|
return DEFAULT_TTL_MS;
|
|
73
|
-
|
|
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),
|
|
74
106
|
};
|
|
75
107
|
let cacheConfig = { ...DEFAULT_CACHE_CONFIG };
|
|
76
108
|
let cachedAt = 0;
|
|
@@ -126,21 +158,33 @@ export async function computeKeyId(publicKeyPem) {
|
|
|
126
158
|
/**
|
|
127
159
|
* Validates a license key JWT and returns the decoded payload.
|
|
128
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.
|
|
129
172
|
*/
|
|
130
|
-
export async function validateLicenseKey(licenseKey, publicKey) {
|
|
173
|
+
export async function validateLicenseKey(licenseKey, publicKey, expectedCustomerId) {
|
|
131
174
|
try {
|
|
175
|
+
const jose = await getJose();
|
|
132
176
|
// Extract kid from JWT header for forward-compatible key rotation
|
|
133
|
-
const header = decodeProtectedHeader(licenseKey);
|
|
177
|
+
const header = jose.decodeProtectedHeader(licenseKey);
|
|
134
178
|
const expectedKid = await computeKeyId(publicKey);
|
|
135
179
|
if (header.kid && header.kid !== expectedKid) {
|
|
136
180
|
logger.warn(`JWT kid mismatch: token has "${header.kid}", current key is "${expectedKid}". ` +
|
|
137
181
|
'Token may have been signed with a rotated key.');
|
|
138
182
|
}
|
|
139
|
-
const key = await importSPKI(publicKey, '
|
|
183
|
+
const key = await jose.importSPKI(publicKey, 'EdDSA');
|
|
140
184
|
// Accept tokens expired within the subscription grace window so the
|
|
141
185
|
// payload is available for grace-period calculations in isLicensed().
|
|
142
|
-
const { payload } = await jwtVerify(licenseKey, key, {
|
|
143
|
-
algorithms: ['
|
|
186
|
+
const { payload } = await jose.jwtVerify(licenseKey, key, {
|
|
187
|
+
algorithms: ['EdDSA'],
|
|
144
188
|
clockTolerance: graceConfig.subscriptionDays * 86_400,
|
|
145
189
|
issuer: LICENSE_ISSUER,
|
|
146
190
|
audience: LICENSE_AUDIENCE,
|
|
@@ -149,6 +193,16 @@ export async function validateLicenseKey(licenseKey, publicKey) {
|
|
|
149
193
|
if (!result.success) {
|
|
150
194
|
return null;
|
|
151
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
|
+
}
|
|
152
206
|
return result.data;
|
|
153
207
|
}
|
|
154
208
|
catch {
|
|
@@ -380,23 +434,35 @@ export function getMaxAgentTasks() {
|
|
|
380
434
|
* `jose.importPKCS8` and `SignJWT` both run on Web Crypto.
|
|
381
435
|
*
|
|
382
436
|
* @param payload - License payload (tier, customerId, limits, perpetual flag)
|
|
383
|
-
* @param privateKey -
|
|
437
|
+
* @param privateKey - Ed25519 private key (PEM format)
|
|
384
438
|
* @param expiresInSeconds - JWT expiration in seconds. Pass null for perpetual
|
|
385
439
|
* licenses (no exp claim). Defaults to 1 year for subscription licenses.
|
|
386
|
-
* @param publicKey -
|
|
440
|
+
* @param publicKey - Ed25519 public key (PEM format). When provided, a `kid`
|
|
387
441
|
* claim is added to the JWT header for forward-compatible key rotation.
|
|
388
442
|
* @returns Signed JWT string
|
|
389
443
|
*/
|
|
390
444
|
export async function generateLicenseKey(payload, privateKey, expiresInSeconds = 365 * 24 * 60 * 60, publicKey) {
|
|
391
|
-
const
|
|
445
|
+
const jose = await getJose();
|
|
446
|
+
const key = await jose.importPKCS8(privateKey, 'EdDSA');
|
|
392
447
|
const kid = publicKey ? await computeKeyId(publicKey) : undefined;
|
|
393
|
-
const header = { alg: '
|
|
448
|
+
const header = { alg: 'EdDSA' };
|
|
394
449
|
if (kid) {
|
|
395
450
|
header.kid = kid;
|
|
396
451
|
}
|
|
397
|
-
|
|
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 })
|
|
398
460
|
.setProtectedHeader(header)
|
|
399
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)
|
|
400
466
|
.setIssuer(LICENSE_ISSUER)
|
|
401
467
|
.setAudience(LICENSE_AUDIENCE);
|
|
402
468
|
if (expiresInSeconds !== null) {
|
|
@@ -162,6 +162,9 @@ export declare const appMetrics: {
|
|
|
162
162
|
memoryUsage: Gauge;
|
|
163
163
|
apiCallsTotal: Counter;
|
|
164
164
|
apiCallDuration: Histogram;
|
|
165
|
+
x402PaymentRequiredTotal: Counter;
|
|
166
|
+
x402PaymentVerifyTotal: Counter;
|
|
167
|
+
x402PaymentVerifyDuration: Histogram;
|
|
165
168
|
};
|
|
166
169
|
/**
|
|
167
170
|
* Track HTTP request
|
|
@@ -183,6 +186,18 @@ export declare function trackError(type: string, severity?: 'low' | 'medium' | '
|
|
|
183
186
|
* Update active connections
|
|
184
187
|
*/
|
|
185
188
|
export declare function updateActiveConnections(type: string, delta: number): void;
|
|
189
|
+
/**
|
|
190
|
+
* Track an x402 HTTP 402 emission at a route. Call when the route returns
|
|
191
|
+
* 402 with `X-PAYMENT-REQUIRED`. `currency` is always `'usdc-only'` — USDC on
|
|
192
|
+
* Base is the sole settlement currency.
|
|
193
|
+
*/
|
|
194
|
+
export declare function trackX402PaymentRequired(route: string, currency: 'usdc-only'): void;
|
|
195
|
+
/**
|
|
196
|
+
* Track an x402 payment verification. Increments the result counter and
|
|
197
|
+
* observes the duration histogram (latency = Coinbase facilitator round-trip
|
|
198
|
+
* for USDC).
|
|
199
|
+
*/
|
|
200
|
+
export declare function trackX402PaymentVerify(route: string, scheme: 'exact', result: 'valid' | 'invalid', durationMs: number): void;
|
|
186
201
|
/**
|
|
187
202
|
* Update memory usage
|
|
188
203
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/observability/metrics.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/observability/metrics.ts"],"names":[],"mappings":"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"}
|
|
@@ -355,6 +355,10 @@ export const appMetrics = {
|
|
|
355
355
|
'status',
|
|
356
356
|
]),
|
|
357
357
|
apiCallDuration: metrics.histogram('api_call_duration_seconds', 'External API call duration', [0.1, 0.5, 1, 2, 5, 10], ['service']),
|
|
358
|
+
// x402 micropayments (GAP-149 PR 5)
|
|
359
|
+
x402PaymentRequiredTotal: metrics.counter('x402_payment_required_total', 'HTTP 402 Payment Required responses emitted by route + currency advertised', ['route', 'currency']),
|
|
360
|
+
x402PaymentVerifyTotal: metrics.counter('x402_payment_verify_total', 'x402 payment verification attempts by route, scheme, and result', ['route', 'scheme', 'result']),
|
|
361
|
+
x402PaymentVerifyDuration: metrics.histogram('x402_payment_verify_duration_seconds', 'x402 payment verification latency (Coinbase facilitator round-trip for USDC)', [0.05, 0.1, 0.5, 1, 5, 10, 30], ['scheme']),
|
|
358
362
|
};
|
|
359
363
|
/**
|
|
360
364
|
* Track HTTP request
|
|
@@ -388,6 +392,23 @@ export function trackError(type, severity = 'medium') {
|
|
|
388
392
|
export function updateActiveConnections(type, delta) {
|
|
389
393
|
appMetrics.activeConnections.inc(delta, { type });
|
|
390
394
|
}
|
|
395
|
+
/**
|
|
396
|
+
* Track an x402 HTTP 402 emission at a route. Call when the route returns
|
|
397
|
+
* 402 with `X-PAYMENT-REQUIRED`. `currency` is always `'usdc-only'` — USDC on
|
|
398
|
+
* Base is the sole settlement currency.
|
|
399
|
+
*/
|
|
400
|
+
export function trackX402PaymentRequired(route, currency) {
|
|
401
|
+
appMetrics.x402PaymentRequiredTotal.inc(1, { route, currency });
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Track an x402 payment verification. Increments the result counter and
|
|
405
|
+
* observes the duration histogram (latency = Coinbase facilitator round-trip
|
|
406
|
+
* for USDC).
|
|
407
|
+
*/
|
|
408
|
+
export function trackX402PaymentVerify(route, scheme, result, durationMs) {
|
|
409
|
+
appMetrics.x402PaymentVerifyTotal.inc(1, { route, scheme, result });
|
|
410
|
+
appMetrics.x402PaymentVerifyDuration.observe(durationMs / 1000, { scheme });
|
|
411
|
+
}
|
|
391
412
|
/**
|
|
392
413
|
* Update memory usage
|
|
393
414
|
*/
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RevForge per-customer license issuance.
|
|
3
|
+
*
|
|
4
|
+
* The Stripe webhook handler (apps/server/src/routes/webhooks.ts) issues licenses
|
|
5
|
+
* for SaaS subscribers. This module is the analogous issuer for self-hosted
|
|
6
|
+
* RevForge customers — paid direct (source-license / sales) and stamped via
|
|
7
|
+
* forge/stamp.sh.
|
|
8
|
+
*
|
|
9
|
+
* Pure function: takes options + Ed25519 keys, returns a signed JWT plus
|
|
10
|
+
* descriptive metadata. The CLI wrapper (scripts/setup/issue-revforge-license.ts)
|
|
11
|
+
* handles argv parsing and reading keys from the environment.
|
|
12
|
+
*/
|
|
13
|
+
import { type LicensePayload } from './license.js';
|
|
14
|
+
export declare const SLUG_PATTERN: RegExp;
|
|
15
|
+
export declare const VALID_REVFORGE_TIERS: readonly ["pro", "max", "enterprise"];
|
|
16
|
+
export type RevForgeTier = (typeof VALID_REVFORGE_TIERS)[number];
|
|
17
|
+
export interface IssueRevForgeLicenseOptions {
|
|
18
|
+
/** Customer slug; becomes the JWT customerId. Must match SLUG_PATTERN. */
|
|
19
|
+
slug: string;
|
|
20
|
+
/** Paid tier the license unlocks. */
|
|
21
|
+
tier: RevForgeTier;
|
|
22
|
+
/** JWT expiry in days. Omit for the 365-day default. Mutually exclusive with `perpetual`. */
|
|
23
|
+
expiresInDays?: number;
|
|
24
|
+
/** One-time purchase: omit `exp` claim entirely. Mutually exclusive with `expiresInDays`. */
|
|
25
|
+
perpetual?: boolean;
|
|
26
|
+
/** Override per-tier site cap. */
|
|
27
|
+
maxSites?: number;
|
|
28
|
+
/** Override per-tier user cap. */
|
|
29
|
+
maxUsers?: number;
|
|
30
|
+
/** Allowed deployment hostnames. */
|
|
31
|
+
domains?: string[];
|
|
32
|
+
}
|
|
33
|
+
export interface IssueRevForgeLicenseResult {
|
|
34
|
+
/** Signed EdDSA JWT — the license key the customer installs. */
|
|
35
|
+
licenseKey: string;
|
|
36
|
+
/** Customer identifier embedded in the JWT (the slug). */
|
|
37
|
+
customerId: string;
|
|
38
|
+
/** Tier embedded in the JWT. */
|
|
39
|
+
tier: RevForgeTier;
|
|
40
|
+
/** Whether this is a perpetual license (no exp claim). */
|
|
41
|
+
perpetual: boolean;
|
|
42
|
+
/** ISO-8601 timestamp of issuance. */
|
|
43
|
+
issuedAt: string;
|
|
44
|
+
/** ISO-8601 expiry, or null for perpetual licenses. */
|
|
45
|
+
expiresAt: string | null;
|
|
46
|
+
/** The payload that was signed (without iat/exp/jti; those live inside the JWT). */
|
|
47
|
+
payload: Omit<LicensePayload, 'iat' | 'exp' | 'jti'>;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Issue a signed RevForge license JWT for a paying customer.
|
|
51
|
+
*
|
|
52
|
+
* @throws Error on invalid slug, invalid tier, mutually-exclusive flag combos,
|
|
53
|
+
* or non-positive numeric overrides. The error message names the offending
|
|
54
|
+
* field so callers can surface it directly to the operator.
|
|
55
|
+
*/
|
|
56
|
+
export declare function issueRevForgeLicense(opts: IssueRevForgeLicenseOptions, keys: {
|
|
57
|
+
privateKey: string;
|
|
58
|
+
publicKey: string;
|
|
59
|
+
}): Promise<IssueRevForgeLicenseResult>;
|
|
60
|
+
//# sourceMappingURL=revforge-license.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revforge-license.d.ts","sourceRoot":"","sources":["../src/revforge-license.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAEvE,eAAO,MAAM,YAAY,QAAyB,CAAC;AAEnD,eAAO,MAAM,oBAAoB,uCAAwC,CAAC;AAC1E,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEjE,MAAM,WAAW,2BAA2B;IAC1C,0EAA0E;IAC1E,IAAI,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,IAAI,EAAE,YAAY,CAAC;IACnB,6FAA6F;IAC7F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,6FAA6F;IAC7F,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,kCAAkC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,kCAAkC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oCAAoC;IACpC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,0BAA0B;IACzC,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAC;IACnB,0DAA0D;IAC1D,UAAU,EAAE,MAAM,CAAC;IACnB,gCAAgC;IAChC,IAAI,EAAE,YAAY,CAAC;IACnB,0DAA0D;IAC1D,SAAS,EAAE,OAAO,CAAC;IACnB,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,oFAAoF;IACpF,OAAO,EAAE,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,KAAK,GAAG,KAAK,CAAC,CAAC;CACtD;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,2BAA2B,EACjC,IAAI,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAC9C,OAAO,CAAC,0BAA0B,CAAC,CAoErC"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RevForge per-customer license issuance.
|
|
3
|
+
*
|
|
4
|
+
* The Stripe webhook handler (apps/server/src/routes/webhooks.ts) issues licenses
|
|
5
|
+
* for SaaS subscribers. This module is the analogous issuer for self-hosted
|
|
6
|
+
* RevForge customers — paid direct (source-license / sales) and stamped via
|
|
7
|
+
* forge/stamp.sh.
|
|
8
|
+
*
|
|
9
|
+
* Pure function: takes options + Ed25519 keys, returns a signed JWT plus
|
|
10
|
+
* descriptive metadata. The CLI wrapper (scripts/setup/issue-revforge-license.ts)
|
|
11
|
+
* handles argv parsing and reading keys from the environment.
|
|
12
|
+
*/
|
|
13
|
+
import { generateLicenseKey } from './license.js';
|
|
14
|
+
export const SLUG_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
15
|
+
export const VALID_REVFORGE_TIERS = ['pro', 'max', 'enterprise'];
|
|
16
|
+
/**
|
|
17
|
+
* Issue a signed RevForge license JWT for a paying customer.
|
|
18
|
+
*
|
|
19
|
+
* @throws Error on invalid slug, invalid tier, mutually-exclusive flag combos,
|
|
20
|
+
* or non-positive numeric overrides. The error message names the offending
|
|
21
|
+
* field so callers can surface it directly to the operator.
|
|
22
|
+
*/
|
|
23
|
+
export async function issueRevForgeLicense(opts, keys) {
|
|
24
|
+
if (!SLUG_PATTERN.test(opts.slug)) {
|
|
25
|
+
throw new Error(`Invalid --slug "${opts.slug}": must match ^[a-z0-9][a-z0-9-]*$ (kebab-case, start with letter or digit).`);
|
|
26
|
+
}
|
|
27
|
+
if (!VALID_REVFORGE_TIERS.includes(opts.tier)) {
|
|
28
|
+
throw new Error(`Invalid --tier "${opts.tier}": must be one of ${VALID_REVFORGE_TIERS.join(', ')}.`);
|
|
29
|
+
}
|
|
30
|
+
if (opts.perpetual && opts.expiresInDays !== undefined) {
|
|
31
|
+
throw new Error('--perpetual and --expires-in-days are mutually exclusive.');
|
|
32
|
+
}
|
|
33
|
+
if (opts.expiresInDays !== undefined) {
|
|
34
|
+
if (!Number.isInteger(opts.expiresInDays) || opts.expiresInDays <= 0) {
|
|
35
|
+
throw new Error(`Invalid --expires-in-days "${opts.expiresInDays}": must be a positive integer.`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (opts.maxSites !== undefined && (!Number.isInteger(opts.maxSites) || opts.maxSites <= 0)) {
|
|
39
|
+
throw new Error(`Invalid --max-sites "${opts.maxSites}": must be a positive integer.`);
|
|
40
|
+
}
|
|
41
|
+
if (opts.maxUsers !== undefined && (!Number.isInteger(opts.maxUsers) || opts.maxUsers <= 0)) {
|
|
42
|
+
throw new Error(`Invalid --max-users "${opts.maxUsers}": must be a positive integer.`);
|
|
43
|
+
}
|
|
44
|
+
// jti is auto-generated inside generateLicenseKey when not supplied (per
|
|
45
|
+
// Phase 1 audit B-2 — every issued token carries a unique JWT ID for
|
|
46
|
+
// forward-compatible per-token revocation).
|
|
47
|
+
const payload = {
|
|
48
|
+
tier: opts.tier,
|
|
49
|
+
customerId: opts.slug,
|
|
50
|
+
...(opts.perpetual ? { perpetual: true } : {}),
|
|
51
|
+
...(opts.maxSites !== undefined ? { maxSites: opts.maxSites } : {}),
|
|
52
|
+
...(opts.maxUsers !== undefined ? { maxUsers: opts.maxUsers } : {}),
|
|
53
|
+
...(opts.domains && opts.domains.length > 0 ? { domains: opts.domains } : {}),
|
|
54
|
+
};
|
|
55
|
+
// Perpetual: pass null to omit exp claim. Otherwise translate days → seconds.
|
|
56
|
+
// Default (neither flag): 365-day subscription, matching generateLicenseKey's default.
|
|
57
|
+
const expiresInSeconds = opts.perpetual
|
|
58
|
+
? null
|
|
59
|
+
: opts.expiresInDays !== undefined
|
|
60
|
+
? opts.expiresInDays * 24 * 60 * 60
|
|
61
|
+
: 365 * 24 * 60 * 60;
|
|
62
|
+
const issuedAtMs = Date.now();
|
|
63
|
+
const licenseKey = await generateLicenseKey(payload, keys.privateKey, expiresInSeconds, keys.publicKey);
|
|
64
|
+
const expiresAt = expiresInSeconds === null ? null : new Date(issuedAtMs + expiresInSeconds * 1000).toISOString();
|
|
65
|
+
return {
|
|
66
|
+
licenseKey,
|
|
67
|
+
customerId: opts.slug,
|
|
68
|
+
tier: opts.tier,
|
|
69
|
+
perpetual: opts.perpetual === true,
|
|
70
|
+
issuedAt: new Date(issuedAtMs).toISOString(),
|
|
71
|
+
expiresAt,
|
|
72
|
+
payload,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Provides server-side rendering components for Lexical content.
|
|
5
5
|
* Converts Lexical JSON state to React elements without requiring a browser.
|
|
6
6
|
*/
|
|
7
|
-
import { isSafeUrl, sanitizeUrl } from '@revealui/security';
|
|
7
|
+
import { isSafeUrl, sanitizeUrl } from '@revealui/security/sanitize';
|
|
8
8
|
import type { SerializedEditorState, SerializedLexicalNode } from 'lexical';
|
|
9
9
|
import { type JSX } from 'react';
|
|
10
10
|
export type { SerializedEditorState };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rsc.d.ts","sourceRoot":"","sources":["../../../../src/richtext/exports/server/rsc.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"rsc.d.ts","sourceRoot":"","sources":["../../../../src/richtext/exports/server/rsc.tsx"],"names":[],"mappings":"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"}
|
|
@@ -5,10 +5,15 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
5
5
|
* Provides server-side rendering components for Lexical content.
|
|
6
6
|
* Converts Lexical JSON state to React elements without requiring a browser.
|
|
7
7
|
*/
|
|
8
|
-
|
|
8
|
+
// Import from the client-safe ./sanitize subpath, NOT the @revealui/security
|
|
9
|
+
// barrel: the barrel statically re-exports auth.ts/gdpr.ts, whose top-level
|
|
10
|
+
// `import 'node:crypto'` crashes this module's client/RSC bundle (blank render
|
|
11
|
+
// on routes that load rich text, e.g. marketing /blog). ./sanitize imports only
|
|
12
|
+
// parse5 and is browser-safe.
|
|
13
|
+
import { isSafeUrl, sanitizeUrl } from '@revealui/security/sanitize';
|
|
9
14
|
import { Fragment } from 'react';
|
|
10
15
|
// URL sanitization is owned by @revealui/security (single source of truth
|
|
11
|
-
// for every untrusted-string sink across
|
|
16
|
+
// for every untrusted-string sink across RevFleet). Re-exported here so
|
|
12
17
|
// existing consumers keep working.
|
|
13
18
|
export { isSafeUrl, sanitizeUrl };
|
|
14
19
|
// ============================================
|
package/dist/security/index.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Security & Compliance -
|
|
3
|
-
*
|
|
2
|
+
* Security & Compliance - full server-side surface.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the client-safe `@revealui/security` barrel AND the server-only
|
|
5
|
+
* `@revealui/security/server` entry (auth/gdpr/audit/ssrf — node: built-ins),
|
|
6
|
+
* so server code can pull any security symbol from one place. Because it
|
|
7
|
+
* re-exports `./server`, THIS entry is server-only: do not import
|
|
8
|
+
* `@revealui/core/security` from client/RSC code (use `@revealui/security` or
|
|
9
|
+
* `@revealui/security/sanitize` there).
|
|
4
10
|
*/
|
|
5
11
|
export * from '@revealui/security';
|
|
12
|
+
export * from '@revealui/security/server';
|
|
6
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/security/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/security/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,cAAc,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC"}
|
package/dist/security/index.js
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Security & Compliance -
|
|
3
|
-
*
|
|
2
|
+
* Security & Compliance - full server-side surface.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the client-safe `@revealui/security` barrel AND the server-only
|
|
5
|
+
* `@revealui/security/server` entry (auth/gdpr/audit/ssrf — node: built-ins),
|
|
6
|
+
* so server code can pull any security symbol from one place. Because it
|
|
7
|
+
* re-exports `./server`, THIS entry is server-only: do not import
|
|
8
|
+
* `@revealui/core/security` from client/RSC code (use `@revealui/security` or
|
|
9
|
+
* `@revealui/security/sanitize` there).
|
|
4
10
|
*/
|
|
5
11
|
export * from '@revealui/security';
|
|
12
|
+
export * from '@revealui/security/server';
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal helpers shared across storage providers.
|
|
3
|
+
*
|
|
4
|
+
* Underscore prefix denotes private-to-storage/; not re-exported from
|
|
5
|
+
* packages/core/src/storage/index.ts.
|
|
6
|
+
*
|
|
7
|
+
* GAP-208 Phase 2a (2026-05-18).
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Normalize the four accepted body types into a Uint8Array so providers
|
|
11
|
+
* can compute size + hand a known buffer to their underlying SDKs.
|
|
12
|
+
*/
|
|
13
|
+
export declare function toUint8Array(data: Blob | ArrayBuffer | Uint8Array | ReadableStream<Uint8Array>): Promise<Uint8Array>;
|
|
14
|
+
//# sourceMappingURL=_helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_helpers.d.ts","sourceRoot":"","sources":["../../src/storage/_helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;GAGG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,IAAI,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,CAAC,UAAU,CAAC,GACjE,OAAO,CAAC,UAAU,CAAC,CA0BrB"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal helpers shared across storage providers.
|
|
3
|
+
*
|
|
4
|
+
* Underscore prefix denotes private-to-storage/; not re-exported from
|
|
5
|
+
* packages/core/src/storage/index.ts.
|
|
6
|
+
*
|
|
7
|
+
* GAP-208 Phase 2a (2026-05-18).
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Normalize the four accepted body types into a Uint8Array so providers
|
|
11
|
+
* can compute size + hand a known buffer to their underlying SDKs.
|
|
12
|
+
*/
|
|
13
|
+
export async function toUint8Array(data) {
|
|
14
|
+
if (data instanceof Uint8Array) {
|
|
15
|
+
return data;
|
|
16
|
+
}
|
|
17
|
+
if (data instanceof ArrayBuffer) {
|
|
18
|
+
return new Uint8Array(data);
|
|
19
|
+
}
|
|
20
|
+
if (data instanceof Blob) {
|
|
21
|
+
return new Uint8Array(await data.arrayBuffer());
|
|
22
|
+
}
|
|
23
|
+
const reader = data.getReader();
|
|
24
|
+
const chunks = [];
|
|
25
|
+
let total = 0;
|
|
26
|
+
while (true) {
|
|
27
|
+
const { done, value } = await reader.read();
|
|
28
|
+
if (done)
|
|
29
|
+
break;
|
|
30
|
+
chunks.push(value);
|
|
31
|
+
total += value.byteLength;
|
|
32
|
+
}
|
|
33
|
+
const merged = new Uint8Array(total);
|
|
34
|
+
let offset = 0;
|
|
35
|
+
for (const chunk of chunks) {
|
|
36
|
+
merged.set(chunk, offset);
|
|
37
|
+
offset += chunk.byteLength;
|
|
38
|
+
}
|
|
39
|
+
return merged;
|
|
40
|
+
}
|