@moontra/moonui-pro 2.37.5 → 2.37.6
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/dist/cdn/index.global.js +1 -1
- package/dist/cdn/index.global.js.map +1 -1
- package/dist/index.mjs +4 -2
- package/dist/server.d.ts +1 -0
- package/dist/server.mjs +4 -2
- package/package.json +2 -4
- package/templates/api-route.template.ts +0 -157
- package/templates/validate-pro-route.ts +0 -475
package/dist/index.mjs
CHANGED
|
@@ -2055,8 +2055,10 @@ function createAIProvider(provider, config) {
|
|
|
2055
2055
|
// src/lib/auth-config.ts
|
|
2056
2056
|
var IS_PRODUCTION = false;
|
|
2057
2057
|
var AUTH_CONFIG = {
|
|
2058
|
-
//
|
|
2059
|
-
|
|
2058
|
+
// CLI Auth Server endpoint (preferred)
|
|
2059
|
+
authServerEndpoint: process.env.NEXT_PUBLIC_MOONUI_AUTH_SERVER || "http://localhost:7878",
|
|
2060
|
+
// Internal API endpoint (fallback only - deprecated)
|
|
2061
|
+
// This will be removed in future versions
|
|
2060
2062
|
internalEndpoint: "/api/moonui/validate-pro",
|
|
2061
2063
|
// Cache configuration
|
|
2062
2064
|
cache: {
|
package/dist/server.d.ts
CHANGED
|
@@ -60,6 +60,7 @@ declare function performServerValidation(): Promise<{
|
|
|
60
60
|
* It ensures zero external API calls from the browser.
|
|
61
61
|
*/
|
|
62
62
|
declare const AUTH_CONFIG: {
|
|
63
|
+
readonly authServerEndpoint: string;
|
|
63
64
|
readonly internalEndpoint: "/api/moonui/validate-pro";
|
|
64
65
|
readonly cache: {
|
|
65
66
|
readonly serverCacheDuration: number;
|
package/dist/server.mjs
CHANGED
|
@@ -2965,8 +2965,10 @@ var import_headers = __toESM(require_headers3(), 1);
|
|
|
2965
2965
|
// src/lib/auth-config.ts
|
|
2966
2966
|
var IS_PRODUCTION = false;
|
|
2967
2967
|
var AUTH_CONFIG = {
|
|
2968
|
-
//
|
|
2969
|
-
|
|
2968
|
+
// CLI Auth Server endpoint (preferred)
|
|
2969
|
+
authServerEndpoint: process.env.NEXT_PUBLIC_MOONUI_AUTH_SERVER || "http://localhost:7878",
|
|
2970
|
+
// Internal API endpoint (fallback only - deprecated)
|
|
2971
|
+
// This will be removed in future versions
|
|
2970
2972
|
internalEndpoint: "/api/moonui/validate-pro",
|
|
2971
2973
|
// Cache configuration
|
|
2972
2974
|
cache: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moontra/moonui-pro",
|
|
3
|
-
"version": "2.37.
|
|
3
|
+
"version": "2.37.6",
|
|
4
4
|
"description": "Premium React components for MoonUI - Advanced UI library with 50+ pro components including performance, interactive, and gesture components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.mjs",
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
"dist",
|
|
13
13
|
"scripts",
|
|
14
14
|
"plugin",
|
|
15
|
-
"templates",
|
|
16
15
|
"README.md",
|
|
17
16
|
"LICENSE"
|
|
18
17
|
],
|
|
@@ -40,8 +39,7 @@
|
|
|
40
39
|
"types": "./plugin/index.d.ts",
|
|
41
40
|
"import": "./plugin/index.js",
|
|
42
41
|
"require": "./plugin/index.js"
|
|
43
|
-
}
|
|
44
|
-
"./templates/validate-pro-route": "./templates/validate-pro-route.ts"
|
|
42
|
+
}
|
|
45
43
|
},
|
|
46
44
|
"scripts": {
|
|
47
45
|
"build": "tsup && node scripts/postbuild.js",
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MoonUI Pro - Server Validation API Route Template
|
|
3
|
-
*
|
|
4
|
-
* INSTALLATION:
|
|
5
|
-
* 1. Copy this file to your project: app/api/moonui/validate-pro/route.ts
|
|
6
|
-
* 2. Set environment variable: MOONUI_LICENSE_KEY=your-license-key
|
|
7
|
-
* 3. (Optional) Set encryption key: MOONUI_ENCRYPTION_KEY=your-secret-key
|
|
8
|
-
*
|
|
9
|
-
* This route handles server-side validation to prevent browser API calls.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { NextRequest, NextResponse } from "next/server";
|
|
13
|
-
import {
|
|
14
|
-
performServerValidation,
|
|
15
|
-
clearValidationCookies,
|
|
16
|
-
generateServerDeviceFingerprint,
|
|
17
|
-
setValidationInCookies,
|
|
18
|
-
} from "@moontra/moonui-pro/server";
|
|
19
|
-
|
|
20
|
-
// In-memory cache for rate limiting (use Redis in production)
|
|
21
|
-
const validationCache = new Map<
|
|
22
|
-
string,
|
|
23
|
-
{
|
|
24
|
-
count: number;
|
|
25
|
-
resetAt: number;
|
|
26
|
-
}
|
|
27
|
-
>();
|
|
28
|
-
|
|
29
|
-
// Rate limiting helper
|
|
30
|
-
function checkRateLimit(identifier: string): boolean {
|
|
31
|
-
const now = Date.now();
|
|
32
|
-
const limit = validationCache.get(identifier);
|
|
33
|
-
|
|
34
|
-
if (!limit || limit.resetAt < now) {
|
|
35
|
-
// Reset or create new limit
|
|
36
|
-
validationCache.set(identifier, {
|
|
37
|
-
count: 1,
|
|
38
|
-
resetAt: now + 60 * 1000, // 1 minute window
|
|
39
|
-
});
|
|
40
|
-
return true;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (limit.count >= 10) {
|
|
44
|
-
// Max 10 requests per minute
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
limit.count++;
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* GET /api/moonui/validate-pro
|
|
54
|
-
* Validates the current session
|
|
55
|
-
*/
|
|
56
|
-
export async function GET(request: NextRequest) {
|
|
57
|
-
try {
|
|
58
|
-
// Generate device fingerprint for rate limiting
|
|
59
|
-
const deviceId = await generateServerDeviceFingerprint();
|
|
60
|
-
|
|
61
|
-
// Check rate limit
|
|
62
|
-
if (!checkRateLimit(deviceId)) {
|
|
63
|
-
return NextResponse.json(
|
|
64
|
-
{ error: "Rate limit exceeded", valid: false },
|
|
65
|
-
{ status: 429 }
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Perform server-side validation
|
|
70
|
-
const validation = await performServerValidation();
|
|
71
|
-
|
|
72
|
-
// Return validation result
|
|
73
|
-
// Client will read from cookies, not from this response
|
|
74
|
-
return NextResponse.json({
|
|
75
|
-
valid: validation.valid,
|
|
76
|
-
hasProAccess: validation.hasProAccess,
|
|
77
|
-
cached: validation.cached,
|
|
78
|
-
});
|
|
79
|
-
} catch (error) {
|
|
80
|
-
console.error("[API] Validation error:", error);
|
|
81
|
-
return NextResponse.json(
|
|
82
|
-
{ error: "Internal server error", valid: false },
|
|
83
|
-
{ status: 500 }
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* POST /api/moonui/validate-pro
|
|
90
|
-
* Validates a provided token
|
|
91
|
-
*/
|
|
92
|
-
export async function POST(request: NextRequest) {
|
|
93
|
-
try {
|
|
94
|
-
const body = await request.json();
|
|
95
|
-
const { token } = body;
|
|
96
|
-
|
|
97
|
-
if (!token) {
|
|
98
|
-
return NextResponse.json(
|
|
99
|
-
{ error: "Token required", valid: false },
|
|
100
|
-
{ status: 400 }
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Generate device fingerprint
|
|
105
|
-
const deviceId = await generateServerDeviceFingerprint();
|
|
106
|
-
|
|
107
|
-
// Check rate limit
|
|
108
|
-
if (!checkRateLimit(deviceId)) {
|
|
109
|
-
return NextResponse.json(
|
|
110
|
-
{ error: "Rate limit exceeded", valid: false },
|
|
111
|
-
{ status: 429 }
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Import server validation utilities
|
|
116
|
-
const { validateWithMoonUIServer } = await import(
|
|
117
|
-
"@moontra/moonui-pro/server"
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
// Validate token with MoonUI server
|
|
121
|
-
const result = await validateWithMoonUIServer(token, deviceId);
|
|
122
|
-
|
|
123
|
-
// If valid, set cookies for future requests
|
|
124
|
-
if (result.valid) {
|
|
125
|
-
await setValidationInCookies(result);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return NextResponse.json(result);
|
|
129
|
-
} catch (error) {
|
|
130
|
-
console.error("[API] Validation error:", error);
|
|
131
|
-
return NextResponse.json(
|
|
132
|
-
{ error: "Internal server error", valid: false },
|
|
133
|
-
{ status: 500 }
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* DELETE /api/moonui/validate-pro
|
|
140
|
-
* Clears validation session
|
|
141
|
-
*/
|
|
142
|
-
export async function DELETE(request: NextRequest) {
|
|
143
|
-
try {
|
|
144
|
-
await clearValidationCookies();
|
|
145
|
-
|
|
146
|
-
return NextResponse.json({
|
|
147
|
-
success: true,
|
|
148
|
-
message: "Validation cleared",
|
|
149
|
-
});
|
|
150
|
-
} catch (error) {
|
|
151
|
-
console.error("[API] Clear validation error:", error);
|
|
152
|
-
return NextResponse.json(
|
|
153
|
-
{ error: "Failed to clear validation" },
|
|
154
|
-
{ status: 500 }
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
@@ -1,475 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MoonUI Pro - Server-side License Validation Route with Enhanced Security
|
|
3
|
-
*
|
|
4
|
-
* This route handles license validation on the server to prevent browser API calls.
|
|
5
|
-
* Copy this file to: app/api/moonui/validate-pro/route.ts (App Router)
|
|
6
|
-
* Or: pages/api/moonui/validate-pro.ts (Pages Router)
|
|
7
|
-
*
|
|
8
|
-
* Required environment variables:
|
|
9
|
-
* - MOONUI_LICENSE_KEY: Your MoonUI Pro license key
|
|
10
|
-
* - MOONUI_ENCRYPTION_KEY (optional): Custom encryption key for cookies
|
|
11
|
-
* - MOONUI_SECURITY_HASH (auto-generated): Security checksum for validation
|
|
12
|
-
*
|
|
13
|
-
* SECURITY WARNING: Do not modify the validation logic.
|
|
14
|
-
* Any tampering will be detected and reported.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
18
|
-
import { cookies, headers } from 'next/headers';
|
|
19
|
-
import crypto from 'crypto';
|
|
20
|
-
import os from 'os';
|
|
21
|
-
import { execSync } from 'child_process';
|
|
22
|
-
|
|
23
|
-
// Security checksum - DO NOT MODIFY
|
|
24
|
-
const SECURITY_CHECKSUM = process.env.MOONUI_SECURITY_HASH ||
|
|
25
|
-
crypto.createHash('sha256')
|
|
26
|
-
.update(`moonui-pro-${process.env.MOONUI_LICENSE_KEY || 'default'}-validation`)
|
|
27
|
-
.digest('hex');
|
|
28
|
-
|
|
29
|
-
// Cache configuration
|
|
30
|
-
const CACHE_DURATION = process.env.NODE_ENV === 'production'
|
|
31
|
-
? 24 * 60 * 60 * 1000 // 24 hours in production
|
|
32
|
-
: 60 * 60 * 1000; // 1 hour in development
|
|
33
|
-
|
|
34
|
-
// Rate limiting
|
|
35
|
-
const rateLimitCache = new Map<string, { count: number; resetAt: number }>();
|
|
36
|
-
|
|
37
|
-
// In-memory validation cache
|
|
38
|
-
const validationCache = new Map<string, {
|
|
39
|
-
valid: boolean;
|
|
40
|
-
hasProAccess: boolean;
|
|
41
|
-
timestamp: number;
|
|
42
|
-
expiresAt: number;
|
|
43
|
-
}>();
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Get MAC address for hardware fingerprinting
|
|
47
|
-
*/
|
|
48
|
-
function getMacAddress(): string {
|
|
49
|
-
try {
|
|
50
|
-
const platform = os.platform();
|
|
51
|
-
let command: string;
|
|
52
|
-
|
|
53
|
-
switch (platform) {
|
|
54
|
-
case 'darwin':
|
|
55
|
-
command = "ifconfig | grep ether | head -1 | awk '{print $2}'";
|
|
56
|
-
break;
|
|
57
|
-
case 'linux':
|
|
58
|
-
command = "ip link show | grep ether | head -1 | awk '{print $2}'";
|
|
59
|
-
break;
|
|
60
|
-
case 'win32':
|
|
61
|
-
command = 'getmac /NH /FO csv | findstr /r "^"';
|
|
62
|
-
break;
|
|
63
|
-
default:
|
|
64
|
-
return 'unknown-mac';
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const result = execSync(command, { encoding: 'utf8' }).trim();
|
|
68
|
-
const match = result.match(/([a-f0-9:]+)/i);
|
|
69
|
-
|
|
70
|
-
if (match && match[1]) {
|
|
71
|
-
return match[1].replace(/[:-]/g, '').toLowerCase();
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
// Fallback to network interfaces
|
|
75
|
-
const interfaces = os.networkInterfaces();
|
|
76
|
-
for (const name in interfaces) {
|
|
77
|
-
const iface = interfaces[name];
|
|
78
|
-
if (iface) {
|
|
79
|
-
for (const entry of iface) {
|
|
80
|
-
if (entry.mac && entry.mac !== '00:00:00:00:00:00') {
|
|
81
|
-
return entry.mac.replace(/[:-]/g, '').toLowerCase();
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
return 'fallback-mac';
|
|
87
|
-
} catch (error) {
|
|
88
|
-
console.error('[Hardware] Error getting MAC:', error);
|
|
89
|
-
return 'error-mac';
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Generate hardware-based device fingerprint
|
|
95
|
-
* Format: hw-{platform}-{macHash}-{cpuHash}
|
|
96
|
-
*/
|
|
97
|
-
function generateHardwareFingerprint(): string {
|
|
98
|
-
const platform = os.platform();
|
|
99
|
-
const macAddress = getMacAddress();
|
|
100
|
-
const cpuCount = os.cpus().length;
|
|
101
|
-
const totalMem = Math.round(os.totalmem() / (1024 * 1024 * 1024)); // GB
|
|
102
|
-
const hostname = os.hostname();
|
|
103
|
-
|
|
104
|
-
// Create hashes
|
|
105
|
-
const macHash = crypto.createHash('sha256')
|
|
106
|
-
.update(macAddress)
|
|
107
|
-
.digest('hex')
|
|
108
|
-
.substring(0, 8);
|
|
109
|
-
|
|
110
|
-
const systemHash = crypto.createHash('sha256')
|
|
111
|
-
.update(`${cpuCount}:${totalMem}:${hostname}`)
|
|
112
|
-
.digest('hex')
|
|
113
|
-
.substring(0, 6);
|
|
114
|
-
|
|
115
|
-
return `hw-${platform}-${macHash}-${systemHash}`;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Generate a device fingerprint from request headers
|
|
120
|
-
* Combines hardware fingerprint with browser characteristics
|
|
121
|
-
*/
|
|
122
|
-
async function getDeviceFingerprint(request: NextRequest): Promise<string> {
|
|
123
|
-
// In development with CLI auth, generate hardware-based fingerprint
|
|
124
|
-
if (process.env.NODE_ENV === 'development') {
|
|
125
|
-
// Use hardware fingerprint for maximum security
|
|
126
|
-
const hardwareId = generateHardwareFingerprint();
|
|
127
|
-
|
|
128
|
-
// If CLI device ID is set, validate it matches hardware
|
|
129
|
-
if (process.env.NEXT_PUBLIC_MOONUI_DEVICE_ID) {
|
|
130
|
-
const cliDeviceId = process.env.NEXT_PUBLIC_MOONUI_DEVICE_ID;
|
|
131
|
-
|
|
132
|
-
// For backward compatibility, accept both old and new format
|
|
133
|
-
if (cliDeviceId.startsWith('hw-')) {
|
|
134
|
-
return cliDeviceId; // New hardware-based format
|
|
135
|
-
} else {
|
|
136
|
-
console.log('[MoonUI] Migrating to hardware-based device ID');
|
|
137
|
-
return hardwareId; // Use new hardware ID
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return hardwareId;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// In production, use hardware fingerprint
|
|
145
|
-
return generateHardwareFingerprint();
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Check rate limiting
|
|
150
|
-
*/
|
|
151
|
-
function checkRateLimit(identifier: string): boolean {
|
|
152
|
-
const now = Date.now();
|
|
153
|
-
const limit = rateLimitCache.get(identifier);
|
|
154
|
-
|
|
155
|
-
if (!limit || limit.resetAt < now) {
|
|
156
|
-
rateLimitCache.set(identifier, {
|
|
157
|
-
count: 1,
|
|
158
|
-
resetAt: now + 60 * 1000, // Reset after 1 minute
|
|
159
|
-
});
|
|
160
|
-
return true;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
if (limit.count >= 10) { // Max 10 requests per minute
|
|
164
|
-
return false;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
limit.count++;
|
|
168
|
-
return true;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Validate license with MoonUI server (server-to-server)
|
|
173
|
-
*/
|
|
174
|
-
async function validateWithMoonUIServer(
|
|
175
|
-
token: string,
|
|
176
|
-
deviceId: string
|
|
177
|
-
): Promise<{ valid: boolean; hasProAccess: boolean; plan?: string }> {
|
|
178
|
-
// Development mode with CLI authentication
|
|
179
|
-
if (process.env.NODE_ENV === 'development') {
|
|
180
|
-
// Check for CLI dev tokens
|
|
181
|
-
const devToken = process.env.NEXT_PUBLIC_MOONUI_DEV_TOKEN;
|
|
182
|
-
const devDeviceId = process.env.NEXT_PUBLIC_MOONUI_DEVICE_ID;
|
|
183
|
-
|
|
184
|
-
console.log('[MoonUI Dev Auth] Checking CLI authentication:');
|
|
185
|
-
console.log('[MoonUI Dev Auth] Device IDs match:', devDeviceId === deviceId);
|
|
186
|
-
|
|
187
|
-
// STRICT DEVICE ID VALIDATION - Prevent token sharing
|
|
188
|
-
if (devToken && devDeviceId && devDeviceId === deviceId) {
|
|
189
|
-
try {
|
|
190
|
-
// Decode the dev token
|
|
191
|
-
const decoded = JSON.parse(Buffer.from(devToken, 'base64').toString());
|
|
192
|
-
|
|
193
|
-
// Verify token structure and session
|
|
194
|
-
if (!decoded.deviceId || !decoded.session || !decoded.security) {
|
|
195
|
-
console.error('[MoonUI Security] Invalid token structure');
|
|
196
|
-
return { valid: false, hasProAccess: false };
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Verify device ID in token matches
|
|
200
|
-
if (decoded.deviceId !== devDeviceId) {
|
|
201
|
-
console.error('[MoonUI Security] Token device ID mismatch');
|
|
202
|
-
return { valid: false, hasProAccess: false };
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Verify session hasn't expired
|
|
206
|
-
if (decoded.session?.expiresAt && decoded.session.expiresAt < Date.now()) {
|
|
207
|
-
console.error('[MoonUI Security] Token session expired');
|
|
208
|
-
return { valid: false, hasProAccess: false };
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
console.log('[MoonUI Dev Auth] Token validated successfully');
|
|
212
|
-
console.log('[MoonUI Dev Auth] User plan:', decoded.user?.plan);
|
|
213
|
-
|
|
214
|
-
if (decoded.user?.plan === 'pro_lifetime' || decoded.user?.hasProAccess) {
|
|
215
|
-
console.log('[MoonUI Dev Auth] Pro access granted via CLI token');
|
|
216
|
-
return {
|
|
217
|
-
valid: true,
|
|
218
|
-
hasProAccess: true,
|
|
219
|
-
plan: decoded.user?.plan || 'lifetime'
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
} catch (e) {
|
|
223
|
-
console.error('[MoonUI Security] Error parsing dev token:', e);
|
|
224
|
-
return { valid: false, hasProAccess: false };
|
|
225
|
-
}
|
|
226
|
-
} else if (devToken && !devDeviceId) {
|
|
227
|
-
console.warn('[MoonUI Security] Token found but no device ID - possible token sharing attempt');
|
|
228
|
-
return { valid: false, hasProAccess: false };
|
|
229
|
-
} else if (devDeviceId !== deviceId) {
|
|
230
|
-
console.warn('[MoonUI Security] Device ID mismatch - token not valid for this device');
|
|
231
|
-
console.warn('[MoonUI Security] This token is locked to device:', devDeviceId);
|
|
232
|
-
console.warn('[MoonUI Security] Current device:', deviceId);
|
|
233
|
-
return { valid: false, hasProAccess: false };
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Production validation with license key
|
|
238
|
-
const licenseKey = process.env.MOONUI_LICENSE_KEY;
|
|
239
|
-
|
|
240
|
-
if (!licenseKey) {
|
|
241
|
-
console.error('[MoonUI] No license key configured');
|
|
242
|
-
return { valid: false, hasProAccess: false };
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
try {
|
|
246
|
-
const response = await fetch('https://api.moonui.dev/v1/license/validate', {
|
|
247
|
-
method: 'POST',
|
|
248
|
-
headers: {
|
|
249
|
-
'Content-Type': 'application/json',
|
|
250
|
-
'Authorization': `Bearer ${licenseKey}`,
|
|
251
|
-
'X-Device-ID': deviceId,
|
|
252
|
-
},
|
|
253
|
-
body: JSON.stringify({
|
|
254
|
-
token: licenseKey,
|
|
255
|
-
deviceId,
|
|
256
|
-
domain: process.env.NEXT_PUBLIC_DOMAIN || 'localhost',
|
|
257
|
-
timestamp: Date.now(),
|
|
258
|
-
}),
|
|
259
|
-
signal: AbortSignal.timeout(10000), // 10 second timeout
|
|
260
|
-
});
|
|
261
|
-
|
|
262
|
-
if (!response.ok) {
|
|
263
|
-
console.error('[MoonUI] License validation failed:', response.status);
|
|
264
|
-
return { valid: false, hasProAccess: false };
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
const data = await response.json();
|
|
268
|
-
|
|
269
|
-
return {
|
|
270
|
-
valid: data.valid,
|
|
271
|
-
hasProAccess: data.valid && (
|
|
272
|
-
data.user?.hasLifetimeAccess ||
|
|
273
|
-
data.user?.features?.includes('pro_components') ||
|
|
274
|
-
data.plan === 'pro_lifetime' ||
|
|
275
|
-
false
|
|
276
|
-
),
|
|
277
|
-
plan: data.user?.plan || data.plan,
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
} catch (error) {
|
|
281
|
-
console.error('[MoonUI] Error validating license:', error);
|
|
282
|
-
return { valid: false, hasProAccess: false };
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Verify request integrity
|
|
288
|
-
*/
|
|
289
|
-
function verifyRequestIntegrity(request: NextRequest): boolean {
|
|
290
|
-
// Check for suspicious patterns
|
|
291
|
-
const url = new URL(request.url);
|
|
292
|
-
|
|
293
|
-
// Reject if trying to bypass with query params
|
|
294
|
-
if (url.searchParams.has('bypass') ||
|
|
295
|
-
url.searchParams.has('force') ||
|
|
296
|
-
url.searchParams.has('admin')) {
|
|
297
|
-
console.warn('[MoonUI Security] Suspicious query params detected');
|
|
298
|
-
return false;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Check request headers for tampering
|
|
302
|
-
const suspiciousHeaders = [
|
|
303
|
-
'x-moonui-bypass',
|
|
304
|
-
'x-force-pro',
|
|
305
|
-
'x-admin-override',
|
|
306
|
-
'x-moonui-device-override',
|
|
307
|
-
'x-moonui-token-override'
|
|
308
|
-
];
|
|
309
|
-
|
|
310
|
-
for (const header of suspiciousHeaders) {
|
|
311
|
-
if (request.headers.has(header)) {
|
|
312
|
-
console.warn(`[MoonUI Security] Suspicious header detected: ${header}`);
|
|
313
|
-
return false;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
// Check for proxy/VPN indicators
|
|
318
|
-
const proxyHeaders = [
|
|
319
|
-
'x-proxy-connection',
|
|
320
|
-
'x-forwarded-server',
|
|
321
|
-
'x-originating-ip',
|
|
322
|
-
'x-remote-ip'
|
|
323
|
-
];
|
|
324
|
-
|
|
325
|
-
const hasProxy = proxyHeaders.some(header => request.headers.has(header));
|
|
326
|
-
if (hasProxy) {
|
|
327
|
-
console.warn('[MoonUI Security] Proxy/VPN detected - additional validation required');
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
return true;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Generate validation signature for response
|
|
335
|
-
*/
|
|
336
|
-
function generateValidationSignature(data: any): string {
|
|
337
|
-
const payload = JSON.stringify({
|
|
338
|
-
...data,
|
|
339
|
-
timestamp: Date.now(),
|
|
340
|
-
checksum: SECURITY_CHECKSUM,
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
return crypto
|
|
344
|
-
.createHash('sha256')
|
|
345
|
-
.update(payload)
|
|
346
|
-
.digest('hex')
|
|
347
|
-
.substring(0, 16);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/**
|
|
351
|
-
* Main API route handler with enhanced security
|
|
352
|
-
*/
|
|
353
|
-
export async function GET(request: NextRequest) {
|
|
354
|
-
try {
|
|
355
|
-
// Security check
|
|
356
|
-
if (!verifyRequestIntegrity(request)) {
|
|
357
|
-
return NextResponse.json(
|
|
358
|
-
{
|
|
359
|
-
error: 'Invalid request',
|
|
360
|
-
valid: false,
|
|
361
|
-
hasProAccess: false
|
|
362
|
-
},
|
|
363
|
-
{ status: 403 }
|
|
364
|
-
);
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
// Generate device fingerprint
|
|
368
|
-
const deviceId = await getDeviceFingerprint(request);
|
|
369
|
-
|
|
370
|
-
// Check rate limiting
|
|
371
|
-
if (!checkRateLimit(deviceId)) {
|
|
372
|
-
return NextResponse.json(
|
|
373
|
-
{ error: 'Rate limit exceeded', valid: false },
|
|
374
|
-
{ status: 429 }
|
|
375
|
-
);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// Check in-memory cache first
|
|
379
|
-
const cacheKey = `validation:${deviceId}`;
|
|
380
|
-
const cached = validationCache.get(cacheKey);
|
|
381
|
-
|
|
382
|
-
if (cached && cached.expiresAt > Date.now()) {
|
|
383
|
-
console.log('[MoonUI] Using cached validation result');
|
|
384
|
-
|
|
385
|
-
return NextResponse.json({
|
|
386
|
-
valid: cached.valid,
|
|
387
|
-
hasProAccess: cached.hasProAccess,
|
|
388
|
-
cached: true,
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// Get license token
|
|
393
|
-
const licenseToken = process.env.MOONUI_LICENSE_KEY ||
|
|
394
|
-
process.env.NEXT_PUBLIC_MOONUI_DEV_TOKEN ||
|
|
395
|
-
'';
|
|
396
|
-
|
|
397
|
-
// Validate with MoonUI server
|
|
398
|
-
const validation = await validateWithMoonUIServer(licenseToken, deviceId);
|
|
399
|
-
|
|
400
|
-
// Cache the result
|
|
401
|
-
const expiresAt = Date.now() + CACHE_DURATION;
|
|
402
|
-
validationCache.set(cacheKey, {
|
|
403
|
-
...validation,
|
|
404
|
-
timestamp: Date.now(),
|
|
405
|
-
expiresAt,
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
// Set status cookie for client (readable)
|
|
409
|
-
const cookieStore = await cookies();
|
|
410
|
-
cookieStore.set('moonui_pro_status', JSON.stringify({
|
|
411
|
-
valid: validation.valid,
|
|
412
|
-
hasProAccess: validation.hasProAccess,
|
|
413
|
-
timestamp: Date.now(),
|
|
414
|
-
}), {
|
|
415
|
-
httpOnly: false, // Client needs to read this
|
|
416
|
-
secure: process.env.NODE_ENV === 'production',
|
|
417
|
-
sameSite: 'lax',
|
|
418
|
-
maxAge: CACHE_DURATION / 1000, // Convert to seconds
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
// Prepare response data
|
|
422
|
-
const responseData = {
|
|
423
|
-
valid: validation.valid,
|
|
424
|
-
hasProAccess: validation.hasProAccess,
|
|
425
|
-
isAuthenticated: validation.valid,
|
|
426
|
-
subscriptionPlan: validation.hasProAccess ? 'lifetime' : 'free',
|
|
427
|
-
subscription: {
|
|
428
|
-
status: validation.hasProAccess ? 'active' : 'inactive',
|
|
429
|
-
plan: validation.hasProAccess ? 'lifetime' : 'free',
|
|
430
|
-
},
|
|
431
|
-
_signature: generateValidationSignature({
|
|
432
|
-
valid: validation.valid,
|
|
433
|
-
hasProAccess: validation.hasProAccess,
|
|
434
|
-
deviceId: deviceId,
|
|
435
|
-
}),
|
|
436
|
-
};
|
|
437
|
-
|
|
438
|
-
// Log security events
|
|
439
|
-
if (validation.hasProAccess) {
|
|
440
|
-
console.log(`[MoonUI Security] Pro access granted for device: ${deviceId.substring(0, 8)}...`);
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
return NextResponse.json(responseData);
|
|
444
|
-
|
|
445
|
-
} catch (error) {
|
|
446
|
-
console.error('[MoonUI] Validation error:', error);
|
|
447
|
-
|
|
448
|
-
return NextResponse.json(
|
|
449
|
-
{
|
|
450
|
-
error: 'Internal server error',
|
|
451
|
-
valid: false,
|
|
452
|
-
hasProAccess: false,
|
|
453
|
-
},
|
|
454
|
-
{ status: 500 }
|
|
455
|
-
);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Support for Pages Router
|
|
460
|
-
export async function handler(req: any, res: any) {
|
|
461
|
-
if (req.method !== 'GET') {
|
|
462
|
-
return res.status(405).json({ error: 'Method not allowed' });
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
// Convert to NextRequest for consistent handling
|
|
466
|
-
const url = new URL(req.url!, `http://${req.headers.host}`);
|
|
467
|
-
const request = new NextRequest(url, {
|
|
468
|
-
headers: req.headers,
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
const response = await GET(request);
|
|
472
|
-
const data = await response.json();
|
|
473
|
-
|
|
474
|
-
return res.status(response.status).json(data);
|
|
475
|
-
}
|