v.1.0.0. initial push of license-lib

This commit is contained in:
Joseph D Thompson 2025-03-19 09:37:30 -05:00
commit b22a1c973c
20 changed files with 433 additions and 0 deletions

7
.env.example Normal file
View 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
View 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
View 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
View 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
View 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;
};

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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 };