Referencia API
Documentación completa de todos los endpoints
Visión General
La API de KeyNexus sigue los principios REST y utiliza JSON para todas las peticiones y respuestas.
Base URL
https://keynexus.es/apiContent-Type
application/jsonAutenticación
Las credenciales se envían en el body de la petición, no en headers:
appIdID de la aplicación (ej: Loi0W4jxVWVcnXqy)secretKeyClave secreta de la aplicaciónContent-Typeapplication/json (header requerido)CookieSesión JWT para endpoints autenticadosAutenticación API
Validación de Licencia (Cliente)
Para validar licencias desde aplicaciones de escritorio o cliente.
curl -X POST https://keynexus.es/api/client \
-H "Content-Type: application/json" \
-d '{
"action": "license",
"appId": "app_xxxxxxxxxxxx",
"secretKey": "sk_xxxxxxxxxxxxxxxxxxxx",
"key": "XXXXX-XXXXX-XXXXX-XXXXX",
"hwid": "hardware_id"
}'Session Token (Usuario Autenticado)
Para obtener información del usuario autenticado en el portal cliente.
curl https://keynexus.es/api/client/me \
-H "Cookie: client_token=eyJhbGciOiJIUzI1NiIs..."Endpoints
API Cliente (Aplicaciones Externas)
/api/clientaction: init | license | login | register | check/api/client/authLogin usuario/contraseña (web)/api/client/logoutCerrar sesión/api/client/meObtener usuario actual/api/client/sessionsVer sesiones activasLicencias
/api/licensesListar licencias/api/licensesCrear licencia manual/api/licenses/generateGenerar múltiples licencias/api/licenses/validateValidar licencia/api/licenses/verifyVerificar licencia/api/licenses/:key/extendExtender licencia/api/licenses/:key/revokeRevocar licenciaUsuarios (Dashboard)
/api/usersListar usuarios de apps/api/applications/:id/usersUsuarios de una app/api/applications/:id/users/:userIdActualizar usuario/api/applications/:id/users/:userIdEliminar usuarioSesiones
/api/sessionsListar sesiones/api/sessionsCrear sesión (interno)/api/sessions/:idRevocar sesiónBlacklist / Whitelist
/api/whitelistListar whitelist/blacklist/api/whitelistAñadir entrada/api/blacklistListar blacklist/api/blacklistAñadir a blacklistFormato de Respuestas
Todas las respuestas siguen un formato consistente:
✓ Init Exitoso
{
"success": true,
"message": "Aplicación inicializada correctamente",
"appName": "MiApp",
"version": "1.0.0",
"freeMode": false
}✓ Licencia Válida
{
"success": true,
"message": "Licencia válida",
"license": {
"type": "time-based",
"expiresAt": "2026-01-04T02:04:24.473Z",
"daysLeft": 30
}
}✗ Licencia Inválida
{
"success": false,
"message": "Licencia no válida"
}✗ HWID Bloqueado
{
"success": false,
"message": "HWID en lista negra"
}Códigos de Error
| Mensaje | HTTP | Descripción |
|---|---|---|
| Aplicación no encontrada | 200 | appId o secretKey incorrectos |
| Licencia no válida | 200 | La licencia no existe |
| Licencia expirada | 200 | La licencia ha expirado |
| Licencia revocada | 200 | La licencia fue revocada por el admin |
| HWID no autorizado | 200 | HWID no coincide con el registrado (HWID lock activo) |
| HWID en lista negra | 200 | El HWID está en la blacklist |
| IP en lista negra | 200 | La IP está en la blacklist |
| Credenciales inválidas | 200 | Usuario o contraseña incorrectos |
| Cuenta baneada | 200 | El usuario está baneado |
success para determinar si la operación fue exitosa.Seguridad
Protección avanzada para tu aplicación
Bloqueo HWID (Hardware ID)
El bloqueo HWID vincula una licencia a un dispositivo específico, previniendo que se comparta o use en múltiples máquinas.
Componentes del HWID
- • ID del procesador (CPU)
- • MAC Address de la red
- • Serial del disco duro
- • Serial de la BIOS
- • ID de la placa base
Modos de Bloqueo
- • Strict: Todos los componentes deben coincidir
- • Flexible: Mayoría de componentes (3/5)
- • Lenient: Al menos 2 componentes
using System.Management;
public static string GetHwid()
{
var components = new List<string>();
// CPU ID
var cpu = new ManagementObjectSearcher("SELECT ProcessorId FROM Win32_Processor");
foreach (var obj in cpu.Get())
components.Add(obj["ProcessorId"]?.ToString() ?? "");
// Disk Serial
var disk = new ManagementObjectSearcher("SELECT SerialNumber FROM Win32_DiskDrive");
foreach (var obj in disk.Get())
components.Add(obj["SerialNumber"]?.ToString() ?? "");
// BIOS Serial
var bios = new ManagementObjectSearcher("SELECT SerialNumber FROM Win32_BIOS");
foreach (var obj in bios.Get())
components.Add(obj["SerialNumber"]?.ToString() ?? "");
// Combinar y hashear
var combined = string.Join("-", components);
using var sha = SHA256.Create();
var hash = sha.ComputeHash(Encoding.UTF8.GetBytes(combined));
return Convert.ToBase64String(hash);
}Seguridad IP
KeyNexus ofrece múltiples capas de protección basadas en IP:
Rate Limiting
Limita peticiones por IP para prevenir ataques de fuerza bruta.
Geo-Blocking
Bloquea acceso desde países específicos.
VPN Detection
Detecta y bloquea conexiones VPN/Proxy.
{
"ipSecurity": {
"rateLimit": {
"enabled": true,
"maxRequests": 60,
"windowMs": 60000
},
"geoBlocking": {
"enabled": true,
"mode": "blacklist",
"countries": ["RU", "CN", "KP"]
},
"vpnDetection": {
"enabled": true,
"action": "block"
},
"ipWhitelist": [
"192.168.1.0/24",
"10.0.0.0/8"
]
}
}Whitelist / Blacklist
Controla el acceso a tu aplicación mediante listas de permitidos y bloqueados:
Whitelist (Permitir)
- IP:
192.168.1.100 - HWID:
abc123... - País:
US, CA, MX - Rango:
10.0.0.0/8
Blacklist (Bloquear)
- IP:
203.0.113.50 - HWID:
xyz789... - País:
*(todos excepto whitelist) - Pattern:
192.168.*.*
curl -X POST https://keynexus.es/api/v1/blacklist \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "ip",
"value": "203.0.113.50",
"reason": "Intento de piratería detectado",
"expiresAt": null
}'Anti-Debug y Protección
Técnicas de Protección
var KeyNexus = new KeyNexusClient(new KeyNexusOptions
{
ApplicationId = "app_xxx",
SecretKey = "key_xxx",
// Opciones de seguridad
Security = new SecurityOptions
{
AntiDebug = true,
AntiVM = true,
IntegrityCheck = true,
EncryptCommunication = true,
// Callback cuando se detecta manipulación
OnTamperDetected = (reason) =>
{
// Reportar al servidor y cerrar
KeyNexus.ReportTamper(reason);
Environment.Exit(1);
}
}
});Integraciones
SDKs y ejemplos para cada lenguaje
C#C# / .NET
dotnet add package KeyNexus.SDK --version 2.0.0using KeyNexus;
using KeyNexus.Models;
class Program
{
static KeyNexusClient KeyNexus;
static async Task Main(string[] args)
{
// Inicializar cliente
KeyNexus = new KeyNexusClient(new KeyNexusOptions
{
ApplicationId = "app_xxxxxxxxxxxx",
SecretKey = "sk_xxxxxxxxxxxxxxxxxxxx",
Version = "1.0.0"
});
// Inicializar aplicación
var initResult = await KeyNexus.InitializeAsync();
if (!initResult.Success)
{
Console.WriteLine($"Error: {initResult.Message}");
return;
}
Console.WriteLine($"App: {initResult.Data.Name} v{initResult.Data.Version}");
// Solicitar licencia al usuario
Console.Write("Ingresa tu licencia: ");
var license = Console.ReadLine();
// Login con licencia
var loginResult = await KeyNexus.LoginWithLicenseAsync(new LicenseLoginRequest
{
LicenseKey = license,
Hwid = HardwareInfo.GetHwid()
});
if (!loginResult.Success)
{
Console.WriteLine($"Login fallido: {loginResult.Message}");
return;
}
var user = loginResult.Data.User;
Console.WriteLine($"¡Bienvenido, {user.Username}!");
Console.WriteLine($"Suscripción expira: {user.SubscriptionExpiry}");
// Tu aplicación continúa aquí...
await RunMainApplication();
}
static async Task RunMainApplication()
{
// Verificar periódicamente que la sesión sigue válida
while (true)
{
var checkResult = await KeyNexus.CheckSessionAsync();
if (!checkResult.Success)
{
Console.WriteLine("Sesión expirada. Cerrando...");
break;
}
// Tu lógica de aplicación aquí
await Task.Delay(60000); // Check cada minuto
}
}
}PyPython
pip install KeyNexus-pythonfrom KeyNexus import KeyNexusClient, HardwareInfo
# Inicializar cliente
client = KeyNexusClient(
application_id="app_xxxxxxxxxxxx",
SECRET_KEY="sk_xxxxxxxxxxxxxxxxxxxx",
version="1.0.0"
)
# Inicializar aplicación
init_result = client.initialize()
if not init_result.success:
print(f"Error: {init_result.message}")
exit(1)
print(f"App: {init_result.data.name} v{init_result.data.version}")
# Login con licencia
license_key = input("Ingresa tu licencia: ")
hwid = HardwareInfo.get_hwid()
login_result = client.login_with_license(license_key, hwid)
if not login_result.success:
print(f"Error: {login_result.message}")
exit(1)
user = login_result.data.user
print(f"¡Bienvenido, {user.username}!")
print(f"Expira: {user.subscription_expiry}")
# Tu aplicación continúa aquí...
def main_loop():
while True:
check = client.check_session()
if not check.success:
print("Sesión expirada")
break
# Tu lógica aquí
import time
time.sleep(60)
main_loop()C++C++
vcpkg install KeyNexus-cpp#include <KeyNexus/KeyNexus.hpp>
#include <iostream>
int main() {
// Inicializar cliente
KeyNexus::Client client(
"app_xxxxxxxxxxxx", // Application ID
"sk_xxxxxxxxxxxxxxxxxxxx", // API Key
"1.0.0" // Version
);
// Inicializar aplicación
auto initResult = client.initialize();
if (!initResult.success) {
std::cerr << "Error: " << initResult.message << std::endl;
return 1;
}
std::cout << "App: " << initResult.data.name << std::endl;
// Solicitar licencia
std::string license;
std::cout << "Licencia: ";
std::cin >> license;
// Login
auto hwid = KeyNexus::Hardware::getHwid();
auto loginResult = client.loginWithLicense(license, hwid);
if (!loginResult.success) {
std::cerr << "Error: " << loginResult.message << std::endl;
return 1;
}
std::cout << "Bienvenido, " << loginResult.data.user.username << "!" << std::endl;
// Tu aplicación continúa aquí...
return 0;
}Rust
[dependencies]
KeyNexus = "2.0"Go
go get github.com/KeyNexus/KeyNexus-goEjemplos JavaScript & React
Implementaciones completas para aplicaciones web con JavaScript vanilla, React, Next.js y más.
SDK Oficial de JavaScript/TypeScript
Instalación
npm install keynexusUso Básico
import KeyNexusClient from 'keynexus';
// Inicializar cliente
const client = new KeyNexusClient({
appId: 'app_xxxxxxxxxxxx',
secretKey: 'sk_xxxxxxxxxxxxxxxxxxxx'
});
// El HWID se genera automáticamente
console.log('HWID:', client.getHWID());
// Validar licencia
try {
const result = await client.validateLicense('XXXXX-XXXXX-XXXXX-XXXXX');
if (result.success) {
console.log('✅ Licencia válida!');
console.log('Tipo:', result.license?.type);
console.log('Expira:', result.license?.expiresAt);
console.log('Días restantes:', result.license?.daysLeft);
}
} catch (error) {
if (error.name === 'InvalidLicenseError') {
console.log('❌ Licencia inválida');
} else if (error.name === 'HWIDMismatchError') {
console.log('❌ HWID no coincide');
} else if (error.name === 'ExpiredLicenseError') {
console.log('❌ Licencia expirada');
}
}
// Login con usuario/contraseña
const loginResult = await client.loginWithPassword('usuario', 'contraseña');
if (loginResult.success) {
console.log('Bienvenido', loginResult.user?.username);
}
// Obtener información del usuario
const userInfo = await client.getUserInfo();
console.log('Email:', userInfo.user?.email);JavaScript Vanilla (Sin SDK)
1. Validar Licencia (Cliente)
async function validateLicense(appId, secretKey, licenseKey, hwid) {
try {
const response = await fetch('https://keynexus.es/api/client', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
action: 'license',
appId: appId,
secretKey: secretKey,
key: licenseKey,
hwid: hwid
})
});
const data = await response.json();
if (data.success) {
console.log('✓ Licencia válida');
console.log('Usuario:', data.user.username);
console.log('Expira:', data.user.subscriptionExpiry);
return data;
} else {
console.error('✗ Licencia inválida:', data.message);
return null;
}
} catch (error) {
console.error('Error al validar licencia:', error);
return null;
}
}
// Uso
const result = await validateLicense(
'app_xxxxxxxxxxxx',
'sk_xxxxxxxxxxxxxxxxxxxx',
'XXXXX-XXXXX-XXXXX-XXXXX',
'hardware-id-12345'
);2. Login Usuario/Contraseña
async function loginUser(appId, secretKey, username, password, hwid) {
try {
const response = await fetch('https://keynexus.es/api/client', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
action: 'login',
appId: appId,
secretKey: secretKey,
username: username,
password: password,
hwid: hwid
})
});
const data = await response.json();
if (data.success) {
// Guardar token en localStorage
localStorage.setItem('client_token', data.token);
console.log('✓ Login exitoso');
return data.user;
} else {
console.error('✗ Login fallido:', data.message);
return null;
}
} catch (error) {
console.error('Error en login:', error);
return null;
}
}
// Uso
const user = await loginUser(
'app_xxxxxxxxxxxx',
'sk_xxxxxxxxxxxxxxxxxxxx',
'usuario123',
'miPassword',
'hardware-id-12345'
);3. Obtener Usuario Actual
async function getCurrentUser() {
try {
const token = localStorage.getItem('client_token');
if (!token) {
console.error('No hay sesión activa');
return null;
}
const response = await fetch('https://keynexus.es/api/client/me', {
headers: {
'Cookie': `client_token=${token}`
}
});
const data = await response.json();
if (data.authenticated) {
console.log('Usuario:', data.user.username);
return data.user;
} else {
console.error('Sesión expirada');
localStorage.removeItem('client_token');
return null;
}
} catch (error) {
console.error('Error al obtener usuario:', error);
return null;
}
}React + Hooks
Custom Hook para Autenticación
import { useState, useEffect } from 'react';
export function useAuth() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
// Configuración
const APP_ID = 'app_xxxxxxxxxxxx';
const SECRET_KEY = 'sk_xxxxxxxxxxxxxxxxxxxx';
const API_URL = 'https://keynexus.es/api';
// Verificar sesión al cargar
useEffect(() => {
checkSession();
}, []);
async function checkSession() {
const token = localStorage.getItem('client_token');
if (!token) {
setLoading(false);
return;
}
try {
const res = await fetch(`${API_URL}/client/me`, {
credentials: 'include'
});
const data = await res.json();
if (data.authenticated) {
setUser(data.user);
} else {
localStorage.removeItem('client_token');
}
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
async function loginWithLicense(licenseKey, hwid) {
setLoading(true);
setError(null);
try {
const res = await fetch(`${API_URL}/client`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'license',
appId: APP_ID,
secretKey: SECRET_KEY,
key: licenseKey,
hwid: hwid
})
});
const data = await res.json();
if (data.success) {
setUser(data.user);
localStorage.setItem('client_token', data.token);
return { success: true };
} else {
setError(data.message);
return { success: false, message: data.message };
}
} catch (err) {
setError(err.message);
return { success: false, message: err.message };
} finally {
setLoading(false);
}
}
async function loginWithPassword(username, password, hwid) {
setLoading(true);
setError(null);
try {
const res = await fetch(`${API_URL}/client`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'login',
appId: APP_ID,
secretKey: SECRET_KEY,
username: username,
password: password,
hwid: hwid
})
});
const data = await res.json();
if (data.success) {
setUser(data.user);
localStorage.setItem('client_token', data.token);
return { success: true };
} else {
setError(data.message);
return { success: false, message: data.message };
}
} catch (err) {
setError(err.message);
return { success: false, message: err.message };
} finally {
setLoading(false);
}
}
async function logout() {
try {
await fetch(`${API_URL}/client/logout`, {
method: 'POST',
credentials: 'include'
});
} catch (err) {
console.error('Error en logout:', err);
} finally {
setUser(null);
localStorage.removeItem('client_token');
}
}
return {
user,
loading,
error,
loginWithLicense,
loginWithPassword,
logout,
isAuthenticated: !!user
};
}Componente de Login
import { useState } from 'react';
import { useAuth } from './useAuth';
export function LoginComponent() {
const { loginWithLicense, loginWithPassword, loading, error } = useAuth();
const [mode, setMode] = useState('license'); // 'license' o 'password'
const [licenseKey, setLicenseKey] = useState('');
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
// Generar HWID simple (en producción usa algo más robusto)
const getHWID = () => {
return `hwid_${navigator.userAgent}_${screen.width}x${screen.height}`;
};
const handleLicenseLogin = async (e) => {
e.preventDefault();
const result = await loginWithLicense(licenseKey, getHWID());
if (result.success) {
alert('✓ Licencia activada correctamente');
} else {
alert(`✗ Error: ${result.message}`);
}
};
const handlePasswordLogin = async (e) => {
e.preventDefault();
const result = await loginWithPassword(username, password, getHWID());
if (result.success) {
alert('✓ Login exitoso');
} else {
alert(`✗ Error: ${result.message}`);
}
};
return (
<div className="max-w-md mx-auto p-6 bg-white rounded-lg shadow">
<h2 className="text-2xl font-bold mb-4">Iniciar Sesión</h2>
{/* Selector de modo */}
<div className="flex gap-2 mb-4">
<button
onClick={() => setMode('license')}
className={`px-4 py-2 rounded ${mode === 'license' ? 'bg-blue-500 text-white' : 'bg-gray-200'}`}
>
Con Licencia
</button>
<button
onClick={() => setMode('password')}
className={`px-4 py-2 rounded ${mode === 'password' ? 'bg-blue-500 text-white' : 'bg-gray-200'}`}
>
Usuario/Contraseña
</button>
</div>
{/* Formulario de Licencia */}
{mode === 'license' && (
<form onSubmit={handleLicenseLogin} className="space-y-4">
<div>
<label className="block text-sm font-medium mb-1">
Clave de Licencia
</label>
<input
type="text"
value={licenseKey}
onChange={(e) => setLicenseKey(e.target.value.toUpperCase())}
placeholder="XXXXX-XXXXX-XXXXX-XXXXX"
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<button
type="submit"
disabled={loading}
className="w-full bg-blue-500 text-white py-2 rounded hover:bg-blue-600 disabled:opacity-50"
>
{loading ? 'Validando...' : 'Activar Licencia'}
</button>
</form>
)}
{/* Formulario de Usuario/Contraseña */}
{mode === 'password' && (
<form onSubmit={handlePasswordLogin} className="space-y-4">
<div>
<label className="block text-sm font-medium mb-1">Usuario</label>
<input
type="text"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">Contraseña</label>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-3 py-2 border rounded"
required
/>
</div>
<button
type="submit"
disabled={loading}
className="w-full bg-blue-500 text-white py-2 rounded hover:bg-blue-600 disabled:opacity-50"
>
{loading ? 'Iniciando...' : 'Iniciar Sesión'}
</button>
</form>
)}
{/* Errores */}
{error && (
<div className="mt-4 p-3 bg-red-100 text-red-700 rounded">
{error}
</div>
)}
</div>
);
}Dashboard de Usuario
import { useAuth } from './useAuth';
export function UserDashboard() {
const { user, logout, loading } = useAuth();
if (loading) {
return <div className="text-center p-8">Cargando...</div>;
}
if (!user) {
return <LoginComponent />;
}
return (
<div className="max-w-2xl mx-auto p-6">
<div className="bg-white rounded-lg shadow p-6">
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold">Mi Cuenta</h2>
<button
onClick={logout}
className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
>
Cerrar Sesión
</button>
</div>
<div className="space-y-4">
<div className="border-b pb-3">
<p className="text-sm text-gray-500">Usuario</p>
<p className="text-lg font-medium">{user.username}</p>
</div>
<div className="border-b pb-3">
<p className="text-sm text-gray-500">Email</p>
<p className="text-lg">{user.email || 'No configurado'}</p>
</div>
<div className="border-b pb-3">
<p className="text-sm text-gray-500">Licencia</p>
<p className="text-lg font-mono">{user.licenseKey || 'Sin licencia'}</p>
</div>
<div className="border-b pb-3">
<p className="text-sm text-gray-500">Suscripción Expira</p>
<p className="text-lg">
{user.subscriptionExpiry
? new Date(user.subscriptionExpiry).toLocaleDateString()
: 'Nunca'}
</p>
</div>
<div className="border-b pb-3">
<p className="text-sm text-gray-500">Estado</p>
<span className={`inline-block px-3 py-1 rounded text-sm font-medium ${
user.status === 'active'
? 'bg-green-100 text-green-700'
: 'bg-red-100 text-red-700'
}`}>
{user.status === 'active' ? 'Activo' : user.status}
</span>
</div>
<div className="border-b pb-3">
<p className="text-sm text-gray-500">Total de Accesos</p>
<p className="text-lg">{user.totalLogins || 0}</p>
</div>
<div>
<p className="text-sm text-gray-500">Último Acceso</p>
<p className="text-lg">
{user.lastLogin
? new Date(user.lastLogin).toLocaleString()
: 'Nunca'}
</p>
</div>
</div>
</div>
</div>
);
}Next.js 14+ (App Router)
Middleware de Autenticación
import { NextResponse } from 'next/server';
export function middleware(request) {
const token = request.cookies.get('client_token')?.value;
const isAuthPage = request.nextUrl.pathname.startsWith('/login');
const isProtectedRoute = request.nextUrl.pathname.startsWith('/dashboard');
// Redirigir a login si no hay token
if (isProtectedRoute && !token) {
return NextResponse.redirect(new URL('/login', request.url));
}
// Redirigir a dashboard si ya está autenticado
if (isAuthPage && token) {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/login']
};Server Action para Login
'use server';
import { cookies } from 'next/headers';
export async function loginWithLicense(licenseKey, hwid) {
try {
const res = await fetch('https://keynexus.es/api/client', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'license',
appId: process.env.KEYNEXUS_APP_ID,
secretKey: process.env.KEYNEXUS_SECRET_KEY,
key: licenseKey,
hwid: hwid
})
});
const data = await res.json();
if (data.success) {
// Guardar cookie
cookies().set('client_token', data.token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7 // 7 días
});
return { success: true, user: data.user };
}
return { success: false, message: data.message };
} catch (error) {
return { success: false, message: error.message };
}
}
export async function logout() {
cookies().delete('client_token');
}Tips & Best Practices
Validar Fecha de Expiración
Cuando valides licencias o usuarios, la API devuelve información sobre la expiración. Aquí te mostramos cómo manejarla correctamente:
JavaScript - Validar Expiración
function isLicenseExpired(expirationDate) {
if (!expirationDate) {
// Sin fecha de expiración = licencia vitalicia
return false;
}
const expiry = new Date(expirationDate);
const now = new Date();
return now > expiry;
}
function getDaysUntilExpiration(expirationDate) {
if (!expirationDate) {
return Infinity; // Licencia vitalicia
}
const expiry = new Date(expirationDate);
const now = new Date();
const diffTime = expiry.getTime() - now.getTime();
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays;
}
// Ejemplo de uso
async function checkLicenseStatus() {
const response = await fetch('https://keynexus.es/api/client', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
action: 'license',
appId: 'app_xxxxxxxxxxxx',
secretKey: 'sk_xxxxxxxxxxxxxxxxxxxx',
key: 'XXXXX-XXXXX-XXXXX-XXXXX',
hwid: 'hardware-id-12345'
})
});
const data = await response.json();
if (data.success) {
const expiresAt = data.license.expiresAt;
if (isLicenseExpired(expiresAt)) {
console.log('❌ Licencia expirada');
return false;
}
const daysLeft = getDaysUntilExpiration(expiresAt);
if (daysLeft === Infinity) {
console.log('✅ Licencia vitalicia');
} else if (daysLeft <= 7) {
console.log(`⚠️ Licencia expira en ${daysLeft} días`);
} else {
console.log(`✅ Licencia válida por ${daysLeft} días más`);
}
return true;
}
return false;
}React - Hook para Expiración
import { useState, useEffect } from 'react';
export function useLicenseExpiration(expirationDate) {
const [status, setStatus] = useState({
isExpired: false,
daysLeft: 0,
isLifetime: false,
showWarning: false
});
useEffect(() => {
if (!expirationDate) {
setStatus({
isExpired: false,
daysLeft: Infinity,
isLifetime: true,
showWarning: false
});
return;
}
const checkExpiration = () => {
const expiry = new Date(expirationDate);
const now = new Date();
const diffTime = expiry.getTime() - now.getTime();
const daysLeft = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
setStatus({
isExpired: daysLeft <= 0,
daysLeft: Math.max(0, daysLeft),
isLifetime: false,
showWarning: daysLeft > 0 && daysLeft <= 7
});
};
checkExpiration();
// Verificar cada hora
const interval = setInterval(checkExpiration, 1000 * 60 * 60);
return () => clearInterval(interval);
}, [expirationDate]);
return status;
}
// Uso en componente
function LicenseStatus({ user }) {
const { isExpired, daysLeft, isLifetime, showWarning } =
useLicenseExpiration(user.subscriptionExpiry);
if (isExpired) {
return (
<div className="bg-red-100 text-red-700 p-3 rounded">
❌ Tu licencia ha expirado
</div>
);
}
if (isLifetime) {
return (
<div className="bg-green-100 text-green-700 p-3 rounded">
✅ Licencia vitalicia
</div>
);
}
if (showWarning) {
return (
<div className="bg-yellow-100 text-yellow-700 p-3 rounded">
⚠️ Tu licencia expira en {daysLeft} días
</div>
);
}
return (
<div className="bg-blue-100 text-blue-700 p-3 rounded">
✅ Licencia activa ({daysLeft} días restantes)
</div>
);
}Python - Validar Expiración
from datetime import datetime, timezone
def is_license_expired(expiration_date):
"""Verifica si una licencia ha expirado"""
if not expiration_date:
# Sin fecha de expiración = licencia vitalicia
return False
expiry = datetime.fromisoformat(expiration_date.replace('Z', '+00:00'))
now = datetime.now(timezone.utc)
return now > expiry
def get_days_until_expiration(expiration_date):
"""Calcula días hasta la expiración"""
if not expiration_date:
return float('inf') # Licencia vitalicia
expiry = datetime.fromisoformat(expiration_date.replace('Z', '+00:00'))
now = datetime.now(timezone.utc)
diff = expiry - now
return max(0, diff.days)
# Ejemplo de uso
import requests
def validate_license(app_id, secret_key, license_key, hwid):
response = requests.post('https://keynexus.es/api/client', json={
'action': 'license',
'appId': app_id,
'secretKey': secret_key,
'key': license_key,
'hwid': hwid
})
data = response.json()
if data['success']:
expires_at = data['license'].get('expiresAt')
if is_license_expired(expires_at):
print('❌ Licencia expirada')
return False
days_left = get_days_until_expiration(expires_at)
if days_left == float('inf'):
print('✅ Licencia vitalicia')
elif days_left <= 7:
print(f'⚠️ Licencia expira en {days_left} días')
else:
print(f'✅ Licencia válida por {days_left} días más')
return True
return False¿Necesitas ayuda? Revisa la sección de Solución de Problemas