v.1.0.0. initial push of license-lib
This commit is contained in:
commit
b22a1c973c
7
.env.example
Normal file
7
.env.example
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
DB_HOST=your-db-host
|
||||||
|
DB_NAME=license_db
|
||||||
|
DB_USER_ADMIN=license_admin
|
||||||
|
DB_PASS_ADMIN=secure_admin_password
|
||||||
|
DB_USER_READER=license_reader
|
||||||
|
DB_PASS_READER=secure_read_password
|
||||||
|
JWT_SECRET=your_jwt_secret
|
144
README.md
Normal file
144
README.md
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
|
||||||
|
# License Key Library
|
||||||
|
|
||||||
|
A simple, secure, and flexible licensing library built for Node.js, React, Next.js, and general JavaScript applications.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Quick Start
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install license-lib
|
||||||
|
|
||||||
|
|
||||||
|
Environment Setup
|
||||||
|
Create a .env file at the root with your database and JWT configuration:
|
||||||
|
|
||||||
|
env
|
||||||
|
JWT_SECRET=your_jwt_secret
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_NAME=license_db
|
||||||
|
DB_USER_ADMIN=license_admin
|
||||||
|
DB_PASS_ADMIN=secure_admin_password
|
||||||
|
DB_USER_READER=license_reader
|
||||||
|
DB_PASS_READER=secure_reader_password
|
||||||
|
|
||||||
|
|
||||||
|
⚙️ Database Setup
|
||||||
|
Use the provided SQL script to set up your database:
|
||||||
|
|
||||||
|
sql
|
||||||
|
Copy code
|
||||||
|
CREATE DATABASE license_db;
|
||||||
|
|
||||||
|
USE license_db;
|
||||||
|
|
||||||
|
CREATE TABLE licenses (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id VARCHAR(255) NOT NULL,
|
||||||
|
hashed_key VARCHAR(255) NOT NULL,
|
||||||
|
license_type ENUM('one-time', 'subscription', 'trial') DEFAULT 'one-time',
|
||||||
|
tier VARCHAR(50) DEFAULT 'basic',
|
||||||
|
valid_until DATETIME DEFAULT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE USER 'license_reader'@'%' IDENTIFIED BY 'secure_reader_password';
|
||||||
|
GRANT SELECT ON license_db.* TO 'license_reader'@'%';
|
||||||
|
|
||||||
|
CREATE USER 'license_admin'@'%' IDENTIFIED BY 'secure_admin_password';
|
||||||
|
GRANT SELECT, INSERT, UPDATE ON license_db.* TO 'license_admin'@'%';
|
||||||
|
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
📦 API Endpoints
|
||||||
|
Generate License
|
||||||
|
|
||||||
|
POST /generate-license
|
||||||
|
|
||||||
|
Body:
|
||||||
|
|
||||||
|
json
|
||||||
|
Copy code
|
||||||
|
{
|
||||||
|
"userId": "user123",
|
||||||
|
"licenseType": "subscription",
|
||||||
|
"tier": "pro",
|
||||||
|
"validUntil": "2024-12-31"
|
||||||
|
}
|
||||||
|
Response:
|
||||||
|
|
||||||
|
json
|
||||||
|
Copy code
|
||||||
|
{
|
||||||
|
"licenseKey": "generated.jwt.token"
|
||||||
|
}
|
||||||
|
Validate License
|
||||||
|
|
||||||
|
POST /validate-license
|
||||||
|
|
||||||
|
Body:
|
||||||
|
|
||||||
|
json
|
||||||
|
Copy code
|
||||||
|
{
|
||||||
|
"userId": "user123",
|
||||||
|
"licenseKey": "generated.jwt.token"
|
||||||
|
}
|
||||||
|
Response:
|
||||||
|
|
||||||
|
json
|
||||||
|
Copy code
|
||||||
|
{
|
||||||
|
"valid": true
|
||||||
|
}
|
||||||
|
🎯 Client Integration
|
||||||
|
Import functions directly from the client module:
|
||||||
|
|
||||||
|
javascript
|
||||||
|
Copy code
|
||||||
|
import { activateLicense, generateFingerprint } from './client';
|
||||||
|
|
||||||
|
// Validate license
|
||||||
|
const valid = await activateLicense(userId, licenseKey);
|
||||||
|
|
||||||
|
// Device fingerprinting
|
||||||
|
const fingerprint = generateFingerprint();
|
||||||
|
⚛️ React Integration
|
||||||
|
Use the provided React hook and provider:
|
||||||
|
|
||||||
|
jsx
|
||||||
|
Copy code
|
||||||
|
import { LicenseProvider } from './react/LicenseProvider';
|
||||||
|
import useLicense from './react/useLicense';
|
||||||
|
|
||||||
|
const App = () => (
|
||||||
|
<LicenseProvider userId="user123" licenseKey="jwt.token">
|
||||||
|
<YourApp />
|
||||||
|
</LicenseProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
const YourApp = () => {
|
||||||
|
const { isLicensed } = useLicense();
|
||||||
|
return <div>{isLicensed ? 'Licensed!' : 'Not Licensed!'}</div>;
|
||||||
|
};
|
||||||
|
▲ Next.js Integration
|
||||||
|
Wrap your component easily:
|
||||||
|
|
||||||
|
jsx
|
||||||
|
Copy code
|
||||||
|
import withLicense from './nextjs/withLicense';
|
||||||
|
|
||||||
|
const Page = ({ isLicensed }) => (
|
||||||
|
<div>{isLicensed ? 'Licensed!' : 'Not Licensed!'}</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default withLicense(Page, { userId: 'user123', licenseKey: 'jwt.token' });
|
||||||
|
📌 License
|
||||||
|
This project is licensed under MIT.
|
||||||
|
|
||||||
|
vbnet
|
||||||
|
Copy code
|
||||||
|
|
||||||
|
Your licensing library is now fully documented and ready for developers to integrate easily into their projects.
|
7
client/config.js
Normal file
7
client/config.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
// client/config.js
|
||||||
|
|
||||||
|
export const CONFIG = {
|
||||||
|
LICENSE_API_URL: 'http://localhost:3000', // backend API URL
|
||||||
|
OFFLINE_GRACE_PERIOD_DAYS: 14,
|
||||||
|
};
|
||||||
|
|
6
client/index.js
Normal file
6
client/index.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// client/index.js
|
||||||
|
|
||||||
|
export { activateLicense } from './validate';
|
||||||
|
export { cacheLicenseData, getCachedLicenseData, isLicenseCacheValid } from './offline';
|
||||||
|
export { generateFingerprint } from './utils/fingerprint';
|
||||||
|
export { CONFIG } from './config';
|
24
client/offline.js
Normal file
24
client/offline.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// client/offline.js
|
||||||
|
|
||||||
|
const CACHE_KEY = 'license_data';
|
||||||
|
|
||||||
|
export const cacheLicenseData = (licenseData) => {
|
||||||
|
localStorage.setItem(CACHE_KEY, JSON.stringify({
|
||||||
|
data: licenseData,
|
||||||
|
cachedAt: Date.now(),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCachedLicenseData = () => {
|
||||||
|
const item = localStorage.getItem(CACHE_KEY);
|
||||||
|
return item ? JSON.parse(item) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const isLicenseCacheValid = () => {
|
||||||
|
const cache = getCachedLicenseData();
|
||||||
|
if (!cache) return false;
|
||||||
|
|
||||||
|
const cachedAt = cache.cachedAt;
|
||||||
|
const elapsedDays = (Date.now() - cachedAt) / (1000 * 60 * 60 * 24);
|
||||||
|
return elapsedDays <= CONFIG.OFFLINE_GRACE_PERIOD_DAYS;
|
||||||
|
};
|
6
client/utils/fingerprint.js
Normal file
6
client/utils/fingerprint.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// client/utils/fingerprint.js
|
||||||
|
|
||||||
|
export const generateFingerprint = () => {
|
||||||
|
return btoa(navigator.userAgent + navigator.language + screen.width + screen.height);
|
||||||
|
};
|
||||||
|
|
14
client/validate.js
Normal file
14
client/validate.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// client/validate.js
|
||||||
|
|
||||||
|
import { CONFIG } from './config';
|
||||||
|
|
||||||
|
export const activateLicense = async (userId, licenseKey) => {
|
||||||
|
const response = await fetch(`${CONFIG.LICENSE_API_URL}/validate-license`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ userId, licenseKey }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { valid } = await response.json();
|
||||||
|
return valid;
|
||||||
|
};
|
11
example/client-example.js
Normal file
11
example/client-example.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// example/client-example.js
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const { data } = await axios.post('http://localhost:3000/validate-license', {
|
||||||
|
userId: 'user123',
|
||||||
|
licenseKey: 'your_license_key_here'
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('License Valid:', data.valid);
|
||||||
|
})();
|
13
example/server-example.js
Normal file
13
example/server-example.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// example/server-example.js
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
(async () => {
|
||||||
|
const { data } = await axios.post('http://localhost:3000/generate-license', {
|
||||||
|
userId: 'user123',
|
||||||
|
licenseType: 'subscription',
|
||||||
|
tier: 'pro',
|
||||||
|
validUntil: '2024-12-31'
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Generated License:', data.licenseKey);
|
||||||
|
})();
|
27
licenses_db_setup.sql
Normal file
27
licenses_db_setup.sql
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
-- Create Database
|
||||||
|
CREATE DATABASE license_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||||
|
|
||||||
|
USE license_db;
|
||||||
|
|
||||||
|
-- Create licenses table
|
||||||
|
CREATE TABLE licenses (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id VARCHAR(255) NOT NULL,
|
||||||
|
hashed_key VARCHAR(255) NOT NULL,
|
||||||
|
license_type ENUM('one-time', 'subscription', 'trial') DEFAULT 'one-time',
|
||||||
|
tier VARCHAR(50) DEFAULT 'basic',
|
||||||
|
valid_until DATETIME DEFAULT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
INDEX idx_user_id (user_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- User with Read-Only Access (Client-side validation)
|
||||||
|
CREATE USER 'license_reader'@'%' IDENTIFIED BY 'secure_read_password';
|
||||||
|
GRANT SELECT ON license_db.licenses TO 'license_reader'@'%';
|
||||||
|
|
||||||
|
-- User with Read-Write Access (Server-side key generation and updating)
|
||||||
|
CREATE USER 'license_admin'@'%' IDENTIFIED BY 'secure_admin_password';
|
||||||
|
GRANT SELECT, INSERT, UPDATE ON license_db.licenses TO 'license_admin'@'%';
|
||||||
|
|
||||||
|
-- Apply privileges
|
||||||
|
FLUSH PRIVILEGES;
|
11
nextjs/withLicense.js
Normal file
11
nextjs/withLicense.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
// nextjs/withLicense.js
|
||||||
|
|
||||||
|
import { LicenseProvider } from '../react/LicenseProvider';
|
||||||
|
|
||||||
|
const withLicense = (Component, { userId, licenseKey }) => (props) => (
|
||||||
|
<LicenseProvider userId={userId} licenseKey={licenseKey}>
|
||||||
|
<Component {...props} />
|
||||||
|
</LicenseProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default withLicense;
|
31
react/LicenseProvider.js
Normal file
31
react/LicenseProvider.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// react/LicenseProvider.js
|
||||||
|
|
||||||
|
import React, { createContext, useState, useEffect } from 'react';
|
||||||
|
import { activateLicense } from '../client/validate';
|
||||||
|
import { cacheLicenseData, getCachedLicenseData, isLicenseCacheValid } from '../client/offline';
|
||||||
|
|
||||||
|
export const LicenseContext = createContext();
|
||||||
|
|
||||||
|
export const LicenseProvider = ({ userId, licenseKey, children }) => {
|
||||||
|
const [isLicensed, setIsLicensed] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const checkLicense = async () => {
|
||||||
|
if (navigator.onLine) {
|
||||||
|
const valid = await activateLicense(userId, licenseKey);
|
||||||
|
setIsLicensed(valid);
|
||||||
|
if (valid) cacheLicenseData({ userId, licenseKey });
|
||||||
|
} else {
|
||||||
|
setIsLicensed(isLicenseCacheValid());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkLicense();
|
||||||
|
}, [userId, licenseKey]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LicenseContext.Provider value={{ isLicensed }}>
|
||||||
|
{children}
|
||||||
|
</LicenseContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
8
react/useLicense.js
Normal file
8
react/useLicense.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// react/useLicense.js
|
||||||
|
|
||||||
|
import { useContext } from 'react';
|
||||||
|
import { LicenseContext } from './LicenseProvider';
|
||||||
|
|
||||||
|
const useLicense = () => useContext(LicenseContext);
|
||||||
|
|
||||||
|
export default useLicense;
|
13
server/config.js
Normal file
13
server/config.js
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// server/config.js
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
JWT_SECRET: process.env.JWT_SECRET,
|
||||||
|
DB_HOST: process.env.DB_HOST,
|
||||||
|
DB_NAME: process.env.DB_NAME,
|
||||||
|
DB_USER_ADMIN: process.env.DB_USER_ADMIN,
|
||||||
|
DB_PASS_ADMIN: process.env.DB_PASS_ADMIN,
|
||||||
|
DB_USER_READER: process.env.DB_USER_READER,
|
||||||
|
DB_PASS_READER: process.env.DB_PASS_READER,
|
||||||
|
LICENSE_CHECK_INTERVAL: '7d',
|
||||||
|
};
|
19
server/database.js
Normal file
19
server/database.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// server/database.js
|
||||||
|
const mysql = require('mysql2/promise');
|
||||||
|
const config = require('./config');
|
||||||
|
|
||||||
|
const adminPool = mysql.createPool({
|
||||||
|
host: config.DB_HOST,
|
||||||
|
user: config.DB_USER_ADMIN,
|
||||||
|
password: config.DB_PASS_ADMIN,
|
||||||
|
database: config.DB_NAME,
|
||||||
|
});
|
||||||
|
|
||||||
|
const readerPool = mysql.createPool({
|
||||||
|
host: config.DB_HOST,
|
||||||
|
user: config.DB_USER_READER,
|
||||||
|
password: config.DB_PASS_READER,
|
||||||
|
database: config.DB_NAME,
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = { adminPool, readerPool };
|
26
server/index.js
Normal file
26
server/index.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// server/index.js
|
||||||
|
const express = require('express');
|
||||||
|
const { generateLicenseKey, hashLicenseKey } = require('./keygen');
|
||||||
|
const { saveLicense } = require('./models/license');
|
||||||
|
const { verifyLicenseKey } = require('./verify');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
app.post('/generate-license', async (req, res) => {
|
||||||
|
const { userId, licenseType, tier, validUntil } = req.body;
|
||||||
|
const licenseKey = generateLicenseKey(userId, licenseType, tier);
|
||||||
|
const hashedKey = hashLicenseKey(JSON.parse(Buffer.from(licenseKey.split('.')[1], 'base64')).rawKey);
|
||||||
|
|
||||||
|
await saveLicense(userId, hashedKey, licenseType, tier, validUntil);
|
||||||
|
|
||||||
|
res.json({ licenseKey });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/validate-license', async (req, res) => {
|
||||||
|
const { userId, licenseKey } = req.body;
|
||||||
|
const valid = await verifyLicenseKey(userId, licenseKey);
|
||||||
|
res.json({ valid });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(3000, () => console.log('License server running on port 3000'));
|
14
server/keygen.js
Normal file
14
server/keygen.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// server/keygen.js
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const { encrypt } = require('./utils/crypto');
|
||||||
|
|
||||||
|
const generateLicenseKey = (userId) => {
|
||||||
|
const rawKey = crypto.randomBytes(32).toString('hex');
|
||||||
|
const payload = { userId, rawKey, issued: Date.now() };
|
||||||
|
const encryptedKey = encrypt(payload);
|
||||||
|
return encryptedKey;
|
||||||
|
};
|
||||||
|
|
||||||
|
const hashLicenseKey = (key) => crypto.createHash('sha256').update(key).digest('hex');
|
||||||
|
|
||||||
|
module.exports = { generateLicenseKey, hashLicenseKey };
|
21
server/models/license.js
Normal file
21
server/models/license.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
const { adminPool, readerPool } = require('../database');
|
||||||
|
|
||||||
|
const saveLicense = async (userId, hashedKey, licenseType, tier, validUntil = null) => {
|
||||||
|
const sql = `
|
||||||
|
INSERT INTO licenses (user_id, hashed_key, license_type, tier, valid_until, created_at)
|
||||||
|
VALUES (?, ?, ?, ?, ?, NOW())
|
||||||
|
`;
|
||||||
|
const [result] = await adminPool.execute(sql, [userId, hashedKey, licenseType, tier, validUntil]);
|
||||||
|
return result.insertId;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLicense = async (userId) => {
|
||||||
|
const sql = `
|
||||||
|
SELECT hashed_key, license_type, tier, valid_until
|
||||||
|
FROM licenses WHERE user_id = ?
|
||||||
|
`;
|
||||||
|
const [rows] = await readerPool.execute(sql, [userId]);
|
||||||
|
return rows[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { saveLicense, getLicense };
|
8
server/utils/crypto.js
Normal file
8
server/utils/crypto.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
// server/utils/crypto.js
|
||||||
|
const jwt = require('jsonwebtoken');
|
||||||
|
const config = require('../config');
|
||||||
|
|
||||||
|
const encrypt = (data) => jwt.sign(data, config.JWT_SECRET);
|
||||||
|
const decrypt = (token) => jwt.verify(token, config.JWT_SECRET);
|
||||||
|
|
||||||
|
module.exports = { encrypt, decrypt };
|
23
server/verify.js
Normal file
23
server/verify.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// server/verify.js
|
||||||
|
const { decrypt } = require('./utils/crypto');
|
||||||
|
const { hashLicenseKey } = require('./keygen');
|
||||||
|
const { getLicense } = require('./models/license');
|
||||||
|
|
||||||
|
const verifyLicenseKey = async (userId, token) => {
|
||||||
|
try {
|
||||||
|
const payload = decrypt(token);
|
||||||
|
if (payload.userId !== userId) return false;
|
||||||
|
|
||||||
|
const storedLicense = await getLicense(userId);
|
||||||
|
const hashed = hashLicenseKey(payload.rawKey);
|
||||||
|
|
||||||
|
if (storedLicense.hashed_key !== hashed) return false;
|
||||||
|
if (storedLicense.license_type !== 'one-time' && storedLicense.valid_until && new Date(storedLicense.valid_until) < new Date()) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { verifyLicenseKey };
|
Loading…
x
Reference in New Issue
Block a user