Skip to content

Commit

Permalink
merge v2 changes
Browse files Browse the repository at this point in the history
  • Loading branch information
tmountjr committed Jun 3, 2024
2 parents 8aeb456 + 04f8714 commit 76ca635
Show file tree
Hide file tree
Showing 8 changed files with 1,192 additions and 95 deletions.
22 changes: 17 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,14 @@ JavaScript implementation of the "Edgio Token" (`ectoken`) - see main repo [ecto

## Install

1. Clone this repo.
2. `cd` into the repo directory
3. Run `npm install`
```
$ npm install @edgio/js-ectoken
```

## Usage

This library is provided in CommonJS (CJS) format. To include the library in a script, `require` it:
```js
const { ECToken, encrypt, decrypt } = require('./ECToken.js')
const { ECToken, encrypt, decrypt } = require('@edgio/js-ectoken')

const ec_token = new ECToken()
ec_token.addValue('ec_country_allow', 'US')
Expand All @@ -33,6 +32,19 @@ const token = await encrypt('my-secret-key', ec_token)
const plaintext = await decrypt('my-secret-key', token)
```

If installing this library as a replacement for [`ectoken-nodejs`](https://github.com/hattan/ectoken-nodejs), import the `V3` namespace instead:

```js
const { V3 } = require('@edgio/js-ectoken')

const token = await V3.encrypt('my-secret-key', 'some_param=valueA&some_other_param=valueB')
const plaintext = await V3.decrypt('my-secret-key', token)
```

**Please Note**: because this version of the token generator uses `crypto.subtle`, the `encrypt` and `decrypt` functions **are now asynchronous.**

The `ECToken` helper class is optional for both the namespaced and non-namespaced import variations. The `encrypt` function will accept either an `ECToken` object or a plain `string`.

## Contribute

We welcome issues, questions, and pull requests.
Expand Down
23 changes: 23 additions & 0 deletions example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const { ECToken, encrypt, decrypt } = require('./index.js');
// Alternatively:
// const { V3 } = require('./index.js')

(async () => {
// Create the token.
const ec_token = new ECToken()
ec_token.addValue('ec_country_allow', 'US')
ec_token.addValue('ec_country_allow', 'CA')
ec_token.addValue('ec_expire', (Date.now() / 1000) + (60 * 60 * 24))

// Encrypt and encode it.
const ec_token_str = await encrypt('my-secret-key', ec_token)
console.log(`Encoded: ${ec_token_str}`)

console.log(' ')

// Now decrypt it back to plaintext.
const plaintext = await decrypt('my-secret-key', ec_token_str)
console.log(`Plaintext: ${plaintext}`)

process.exit()
})();
29 changes: 9 additions & 20 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,10 @@
const { ECToken, encrypt, decrypt } = require('./ECToken.js');
'use strict';
const { V3, encrypt, decrypt } = require('./lib/crypto')
const { ECToken } = require('./lib/ECToken')

(async () => {
// Create the token.
const ec_token = new ECToken()
ec_token.addValue('ec_country_allow', 'US')
ec_token.addValue('ec_country_allow', 'CA')
ec_token.addValue('ec_expire', (Date.now() / 1000) + (60 * 60 * 24))

// Encrypt and encode it.
const ec_token_str = await encrypt('my-secret-key', ec_token)
console.log(`Encoded: ${ec_token_str}`)

console.log(' ')

// Now decrypt it back to plaintext.
const plaintext = await decrypt('my-secret-key', ec_token_str)
console.log(`Plaintext: ${plaintext}`)

process.exit()
})()
module.exports = {
V3,
ECToken,
encrypt,
decrypt
}
73 changes: 73 additions & 0 deletions lib/ECToken.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/**
* A class to manage Edgecast Token creation.
*/
class ECToken {
constructor() {
// Set default values for all valid token fields.
this.values = {
ec_expire: 0,
ec_country_allow: [],
ec_country_deny: [],
ec_url_allow: [],
ec_host_allow: [],
ec_host_deny: [],
ec_ref_allow: [],
ec_ref_deny: [],
ec_clientip: [],
ec_proto_allow: [],
ec_proto_deny: []
}
}

/**
* Set or append a value to the token.
* @param {String} key The key to set or append.
* @param {String} value The value to add.
* @returns None
*/
addValue(key, value) {
if (!(key in this.values)) {
throw new Error(`Invalid key: ${key}`)
}

if (key === 'ec_expire') {
this.values.ec_expire = parseInt(value)
return
}

// All other keys can be multivalue.
if (!this.values[key].includes(value)) {
this.values[key].push(value)
}
}

/**
* @deprecated since v2.0.0; use toString() instead.
*/
serialize() {
return this.toString()
}

/**
* Serialize the token object as a valid EdgeCast Token.
* @returns {String}
*/
toString() {
let token = Object.keys(this.values).reduce((acc, curr) => {
if (curr === 'ec_expire' && this.values.ec_expire && this.values.ec_expire > 0) {
acc.push(`ec_expire=${this.values.ec_expire}`)
} else {
if (this.values[curr].length) {
acc.push(`${curr}=${this.values[curr].join(',')}`)
}
}
return acc
}, []).join('&')
return token
}
}

// Export the ECToken helper class.
module.exports = {
ECToken
}
78 changes: 11 additions & 67 deletions ECToken.js → lib/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,76 +4,14 @@ const base64url = require('base64url')
const iv_size_bytes = 12

/**
* An EdgeCast Token
*/
class ECToken {
constructor() {
// Set default values for all valid token fields.
this.values = {
ec_expire: 0,
ec_country_allow: [],
ec_country_deny: [],
ec_url_allow: [],
ec_host_allow: [],
ec_host_deny: [],
ec_ref_allow: [],
ec_ref_deny: [],
ec_clientip: [],
ec_proto_allow: [],
ec_proto_deny: []
}
}

/**
* Set or append a value to the token.
* @param {String} key The key to set or append.
* @param {String} value The value to add.
* @returns None
*/
addValue(key, value) {
if (!(key in this.values)) {
throw new Error(`Invalid key: ${key}`)
}

if (key === 'ec_expire') {
this.values.ec_expire = parseInt(value)
return
}

// All other keys can be multivalue.
if (!this.values[key].includes(value)) {
this.values[key].push(value)
}
}

/**
* Serialize the token object as a valid EdgeCast Token.
* @returns {String} The serialized output containing all the options the user set.
*/
serialize() {
let token = Object.keys(this.values).reduce((acc, curr) => {
if (curr === 'ec_expire' && this.values.ec_expire && this.values.ec_expire > 0) {
acc.push(`ec_expire=${this.values.ec_expire}`)
} else {
if (this.values[curr].length) {
acc.push(`${curr}=${this.values[curr].join(',')}`)
}
}
return acc
}, []).join('&')
return token
}
}

/**
* Encrypt an ECToken with a key.
* Encrypt an ECToken string with a key.
* @param {String} key The secret key to encode the token.
* @param {ECToken} token The token to encrypt.
* @param {ECToken|string} token The token to encrypt.
* @param {Boolean} verbose Whether to print verbose output.
* @returns {Promise<String>} The encrypted token.
*/
async function encrypt(key, token, verbose = false) {
const token_str = new TextEncoder().encode(token.serialize())
const token_str = new TextEncoder().encode(token.toString())
const key_encoded = new TextEncoder().encode(key)

const key_digest = await crypto.subtle.digest('SHA-256', key_encoded)
Expand Down Expand Up @@ -160,8 +98,14 @@ async function decrypt(key, token, verbose = false) {
return new TextDecoder().decode(decrypted_str)
}

// For backwards compatibility with Verizon ectoken, namespace the encrypt/decrypt
// functions, but also export them individually for backwards compatibility with
// this library.
module.exports = {
ECToken,
V3: {
encrypt,
decrypt
},
encrypt,
decrypt
}
}
Loading

0 comments on commit 76ca635

Please sign in to comment.