Last active
April 10, 2018 23:35
-
-
Save therebelrobot/f2ba1c3c05562510bee3191f213e7805 to your computer and use it in GitHub Desktop.
Uservoice SSO Token Generation (Node 8.x)
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 crypto = require('crypto'); | |
/** | |
* Convert a valid Uservoice user object into an SSO token | |
* | |
* Uservoice documentation: https://developer.uservoice.com/docs/single-sign-on/single-sign-on/ | |
* | |
* ##### documented on 4/10/2018, 4:29:17 PM | |
* | |
* #### gotchas | |
* Make sure the user object contains the required fields specified in the documentation | |
* | |
* #### example | |
* ``` | |
* const uvToken = new UserVoiceToken(ssoKey, subdomain) | |
* const user = { | |
* guid: guid, | |
* display_name: displayName, | |
* email: email, | |
* expires: moment().utc().add(2, 'minutes').format(YYYY-MM-DDTHH:mm:ss) | |
* } | |
* const token = uvToken.generate(user) | |
* const urlForSSO = `https://feedback.uservoice.com?sso=${token}` | |
* ``` | |
*/ | |
class UserVoiceToken { | |
private iv; | |
private key; | |
private ssoKey; | |
private subdomain; | |
private algorithm = 'aes-128-cbc'; | |
private ivText = 'OpenSSL for Ruby'; | |
constructor(ssoKey, subdomain) { | |
if (!ssoKey) { throw new Error('No SSO Key provided.'); } | |
if (typeof ssoKey !== 'string') { throw new Error('SSO Key needs to be a string.'); } | |
if (!subdomain) { throw new Error('No Subdomain provided.'); } | |
if (typeof subdomain !== 'string') { throw new Error('Subdomain needs to be a string.'); } | |
this.ssoKey = ssoKey; | |
this.subdomain = subdomain; | |
this.iv = Buffer.from(this.ivText); | |
this.key = this.generateCypherKey(`${this.ssoKey}${this.subdomain}`); | |
} | |
public generate(user) { | |
const userJSON = JSON.stringify(user); | |
const encryptedJSON = this.encrypt(userJSON); | |
const websafeEncryptedJSON = this.webSafe64(encryptedJSON); | |
return websafeEncryptedJSON; | |
} | |
private generateCypherKey (password) { | |
const hash = crypto.createHash('sha1'); | |
const encodedHash = hash.update(password, 'utf8'); | |
const digest = encodedHash.digest(); | |
return Buffer.from(digest).slice(0, 16); | |
} | |
private encrypt (text) { | |
const cipher = crypto.createCipheriv(this.algorithm, this.key, this.iv); | |
const xored = Buffer.from(text); | |
for (let i = 0; i < 16; xored[i] = xored[i] ^ this.iv[i++]); // xor the iv into the first 16 bytes. This is documented literally nowhere. | |
const encrypted = cipher.update(xored, null, 'base64') + cipher.final('base64'); | |
return encrypted; | |
} | |
private webSafe64 (base64) { | |
return base64.split('/').map(encodeURIComponent).join('/'); | |
} | |
} | |
module.exports = UserVoiceToken; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment