Skip to content

Instantly share code, notes, and snippets.

@ezekg
Last active September 18, 2020 10:14
Show Gist options
  • Save ezekg/f021009b4c419f2462f3706a4478f200 to your computer and use it in GitHub Desktop.
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.
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()
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