Last active
September 18, 2020 10:14
-
-
Save ezekg/f021009b4c419f2462f3706a4478f200 to your computer and use it in GitHub Desktop.
How to generate an offline-capable license resource, as well as an example of how to validate the license both online and offline. Do note that anything outside of expiry validation cannot be done offline e.g. machine validation, scope requirements, etc. Shell script implementation: https://gist.github.com/ezekg/57d1508fc1557236fe5eb8803e0e47af.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const fetch = require('node-fetch') | |
const NodeRSA = require('node-rsa') | |
const crypto = require('crypto') | |
const now = new Date() | |
// The policy we want to use for generated licenses, as well as the | |
// user the license is for. Feel free to omit the user. | |
const POLICY_ID = '3bc8bc4c-78fd-4d8b-89dd-f4d2cda1d353' | |
const USER_ID = '4796e950-0dcf-4bab-9443-8b406889356f' | |
// Our license key, which includes additional information we can use | |
// when validating it offline. Make sure the expiry here matches the | |
// policy's expiry, so online/offline license validations return the | |
// same result. | |
const LICENSE_KEY = JSON.stringify({ | |
key: crypto.randomBytes(8).toString('hex').split(/(.{4})/).filter(Boolean).join('-'), | |
expiry: +new Date(now.setFullYear(now.getFullYear() + 1)) | |
}) | |
// NOTE: Do not use this private key within your app. Generate a new | |
// one. THIS IS ONLY FOR EXAMPLE PURPOSES. | |
const PRIVATE_KEY = | |
`-----BEGIN RSA PRIVATE KEY----- | |
MIIBPAIBAAJBAKGB3pm05k4P3qMSDaVHo5WHFVBH+PMzQT2xTqK6pwxnBphwVhmp | |
Rt4jYSPBLb0EncMTuespsxsVcNHFKvUuWN0CAwEAAQJANPRP+DXIDXBGn1EcCEUk | |
7bIM2vW+On9jtMad8d0hSH85uI2bNV+D7F1kU36m1wWANhmM8H2OolNlhhjWnaWs | |
QQIhANXCbKhpNIS9nYjaKvOnC18pT3QtyclgP/f0xMS/08ZDAiEAwWwiUrqfJ8aJ | |
E7/s1cDPmofsRBhdcgXpxlTreG4zwl8CIQDSvhAqKS6h/98kYRMfjHzloPC4dbwP | |
UVk/uI2V/BlpyQIhAKzKHvdsxBVSZM6B298wecWAu24xnfjok2icICbbu91dAiEA | |
mYvFqfj3Y9/+DMypt/le5NLYjAlJTL5kslAn3wufoeI= | |
-----END RSA PRIVATE KEY-----` | |
async function main() { | |
const rsa = new NodeRSA(PRIVATE_KEY) | |
const enc = rsa.encryptPrivate(LICENSE_KEY, 'base64') | |
let res | |
// NOTE: Using the demo user is only for example purposes - instead, it would | |
// be better if you used e.g. a product token for license creation. | |
const creds = new Buffer('[email protected]:demo').toString('base64') | |
res = await fetch('https://api.keygen.sh/v1/accounts/demo/tokens', { | |
method: 'POST', | |
headers: { | |
'Accept': 'application/vnd.api+json', | |
'Authorization': `Basic ${creds}` | |
} | |
}) | |
const { data: token, errors } = await res.json() | |
if (errors) { | |
console.error('Failed to authenticate with Keygen') | |
return | |
} | |
// Create a new license using our encrypted key as its key attribute | |
res = await fetch('https://api.keygen.sh/v1/accounts/demo/licenses', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/vnd.api+json', | |
'Accept': 'application/vnd.api+json', | |
'Authorization': `Bearer ${token.attributes.token}` | |
}, | |
body: JSON.stringify({ | |
data: { | |
type: 'licenses', | |
attributes: { | |
key: enc | |
}, | |
relationships: { | |
policy: { | |
data: { type: 'policies', id: POLICY_ID } | |
}, | |
user: { | |
data: { type: 'users', id: USER_ID } | |
} | |
} | |
} | |
}) | |
}) | |
const { data } = await res.json() | |
if (data) { | |
console.log(`Created license with key: ${data.attributes.key} (${data.id})`) | |
} else { | |
console.log(`Failed to create license with key: ${enc}`) | |
} | |
} | |
main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const isOnline = require('is-online') | |
const fetch = require('node-fetch') | |
const NodeRSA = require('node-rsa') | |
const now = new Date() | |
// Get this license key from the current user (or e.g. from a local cache). | |
// It should be a base64 encoded payload encrypted with the *private* key. | |
const LICENSE_KEY = 'UcCZlhcoCBcjOgjW7THUcSUoEwFvBHibZd0ajHb2B2gi9DteY5hdjzC4wt7txrOdLuHhsOv4Z3eOFKB5fSc2UgAHRwSZ6ZrQoXicX3e9I8uZ0T5Jm9EiRZG9dr4I4kaP6vEez9fwP59Km0JrzNQZ6s7qEn6lWIuuJurCmUPfUaFjUGn8UZ5sVMGHE4RwHcp3JxnaQUIfWm0Pr51xkYuvUuqhjn8NFLrjZSugr9M3jEUh8C09IQPe3twyIce7Pc2U' | |
// Embed the *public* key within your app so that licenses can be validated | |
// while the user is offline. DO NOT SHARE THE *PRIVATE* KEY. | |
const PUBLIC_KEY = | |
`-----BEGIN PUBLIC KEY----- | |
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKGB3pm05k4P3qMSDaVHo5WHFVBH+PMz | |
QT2xTqK6pwxnBphwVhmpRt4jYSPBLb0EncMTuespsxsVcNHFKvUuWN0CAwEAAQ== | |
-----END PUBLIC KEY-----` | |
async function main() { | |
const isConnectedToInternet = await isOnline() | |
let isValid | |
// If we're online, validate license key against the API | |
if (isConnectedToInternet) { | |
const res = await fetch('https://api.keygen.sh/v1/accounts/demo/licenses/actions/validate-key', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/vnd.api+json', | |
'Accept': 'application/vnd.api+json' | |
}, | |
body: JSON.stringify({ | |
meta: { key: LICENSE_KEY } | |
}) | |
}) | |
const { meta } = await res.json() | |
if (meta) { | |
isValid = meta.valid | |
} else { | |
isValid = false | |
} | |
} else { | |
// Otherwise, decrypt the license key payload and check the expiry | |
try { | |
const rsa = new NodeRSA(PUBLIC_KEY) | |
const dec = rsa.decryptPublic(LICENSE_KEY, 'base64') | |
if (dec) { | |
const data = JSON.parse(Buffer.from(dec, 'base64').toString('ascii')) | |
isValid = data.expiry > now | |
} else { | |
isValid = false | |
} | |
} catch(e) { | |
isValid = false | |
} | |
} | |
if (isValid) { | |
console.log(`License key ${LICENSE_KEY} is valid (currently ${isConnectedToInternet ? 'online' : 'offline'})`) | |
} else { | |
console.log(`License key ${LICENSE_KEY} is not valid (currently ${isConnectedToInternet ? 'online' : 'offline'})`) | |
} | |
} | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment