Skip to content

Commit

Permalink
Merge pull request #62 from movableink/ds/support-for-rsa-token
Browse files Browse the repository at this point in the history
feat: support for RSA Token
  • Loading branch information
shyshy authored Oct 11, 2022
2 parents bf920d5 + b9188e2 commit 8c95e48
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 3 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Data Source is a JS library meant to help developers access Movable Ink Data Sou
- [Details on how Sorcerer determines priority](#details-on-how-sorcerer-determines-priority)
- [Publishing package:](#publishing-package)
- [Changelog](#changelog)
- [3.2.0](#320)
- [3.1.0](#310)
- [3.0.0](#300)
- [2.0.0](#200)
Expand Down Expand Up @@ -333,6 +334,10 @@ $ npm publish

## Changelog

### 3.2.0

- Adds RSA Signature support via `RsaToken` token utility class

### 3.1.0

- Creates `RequestBuilder` and "Token" type utility classes
Expand Down
37 changes: 36 additions & 1 deletion docs/token-builder.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ In `sorcerer`, the Token Parser will extract the tokens from the request body an
- [ReplaceLargeToken](#replacelargetoken)
- [SecretToken](#secrettoken)
- [HmacToken](#hmactoken)
- [RsaToken](#rsatoken)
- [Sha1Token](#sha1token)
- [RequestBuilder](#requestbuilder)
- [Generating a request payload](#generating-a-request-payload)
Expand All @@ -37,6 +38,7 @@ Currently supported tokens are:
- [ReplaceLargeToken](#replacelargetoken)
- [SecretToken](#secrettoken)
- [HmacToken](#hmactoken)
- [RsaToken](#rsatoken)
- [Sha1Token](#sha1token)


Expand Down Expand Up @@ -90,7 +92,9 @@ const tokenModel = new SecretToken(params);
```

### HmacToken
Replaces token with an HMAC signature.
Replaces token with an HMAC signature. Used in conjunction with a `secretName` parameter which corresponds to a secret stored on a data source.

HMAC uses symmetric encryption which means the signature requires a shared secret on the Data Source (reference via `secretName`) that the origin API will have a copy of and use to verify.

**Params**
- **options** (required)
Expand All @@ -117,6 +121,37 @@ const params = {
const tokenModel = new HmacToken(params);
```

### RsaToken
Replaces token with an RSA signature. Used in conjunction with a `secretName` parameter which corresponds to a secret stored on a data source.

RSA uses asymmetric encryption which means the signature requires a RSA keypair. The private key is typically stored on the Data Source (reference via `secretName`) whereas the public key is given to
the origin API's owner to use to verify requests.

**Params**
- **options** (required)
- **stringToSign** (optional) - any string that will be used when generating an RSA signature
- **algorithm** (required)- the hashing algorithm: `sha1` , `sha256`, `md5`
- **secretName** (required) - name of the data source secret (e.g. `watson`)
- **encoding** (required) - option to encode the signature once it is generated: `hex`, `base64`, `base64url`, `base64percent`
- `base64url` produces the same result as `base64` but in addition also replaces `+` with `-` , `/` with `_` , and removes the trailing padding character `=`
- `base64percent` encodes the signature as `base64` and then also URI percent encodes it

**Example:**

```jsx
const params = {
name: 'rsa_sig',
options: {
stringToSign: 'some_message',
algorithm: 'sha1',
secretName: 'watson',
encoding: 'hex',
},
};

const tokenModel = new RsaToken(params);
```

### Sha1Token

Replaces token with a SHA-1 signature.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@movable-internal/data-source.js",
"version": "3.1.0",
"version": "3.2.0",
"main": "./dist/index.js",
"module": "./dist/index.es.js",
"license": "MIT",
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export {
ReplaceLargeToken,
SecretToken,
HmacToken,
RsaToken,
Sha1Token,
} from './token-builder/types';

Expand Down
31 changes: 31 additions & 0 deletions src/token-builder/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,37 @@ export class HmacToken extends TokenBase {
}
}

export class RsaToken extends TokenBase {
constructor(params) {
super(params);
this.type = 'rsa';
this.rsaOptions = params.options || {};
this.validateOptions();
}

toJSON() {
const json = super.toJSON();

return { ...json, options: this.rsaOptions };
}

validateOptions() {
super.validateOptions();

if (!ALLOWED_ALGOS.has(this.rsaOptions.algorithm)) {
this.errors.push('RSA algorithm is invalid');
}

if (!this.rsaOptions.secretName) {
this.errors.push('RSA secret name not provided');
}

if (!ALLOWED_ENCODINGS.has(this.rsaOptions.encoding)) {
this.errors.push('RSA encoding is invalid');
}
}
}

export class Sha1Token extends TokenBase {
constructor(params) {
super(params);
Expand Down
40 changes: 39 additions & 1 deletion test/token-builder/request-builder-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
ReplaceLargeToken,
SecretToken,
HmacToken,
RsaToken,
Sha1Token,
} from '../../src/token-builder/types';
const { test, module } = QUnit;
Expand Down Expand Up @@ -42,6 +43,18 @@ module('RequestBuilder', function () {
};
const hmacToken = new HmacToken(hmacOptions);

const rsaOptions = {
name: 'rsa_sig',
cacheOverride: 'xyz',
options: {
stringToSign: 'mystring',
algorithm: 'sha1',
secretName: 'watson',
encoding: 'hex',
},
};
const rsaToken = new RsaToken(rsaOptions);

const sha1Token = new Sha1Token({
name: 'sha1_sig',
options: {
Expand All @@ -56,6 +69,7 @@ module('RequestBuilder', function () {
replaceLargeToken,
secretToken,
hmacToken,
rsaToken,
sha1Token,
]);

Expand Down Expand Up @@ -95,6 +109,18 @@ module('RequestBuilder', function () {
stringToSign: 'mystring',
},
},
{
name: 'rsa_sig',
type: 'rsa',
cacheOverride: 'xyz',
skipCache: false,
options: {
algorithm: 'sha1',
encoding: 'hex',
secretName: 'watson',
stringToSign: 'mystring',
},
},
{
name: 'sha1_sig',
type: 'sha1',
Expand Down Expand Up @@ -136,6 +162,16 @@ module('RequestBuilder', function () {
};
const hmacToken = new HmacToken(hmacOptions);

const rsaOptions = {
cacheOverride: 'xyz',
options: {
stringToSign: 'mystring',
algorithm: 'ash1',
encoding: 'lex',
},
};
const rsaToken = new RsaToken(rsaOptions);

const sha1Token = new Sha1Token({
name: 'sha1_sig',
options: {
Expand All @@ -150,6 +186,7 @@ module('RequestBuilder', function () {
replaceLargeToken,
secretToken,
hmacToken,
rsaToken,
sha1Token,
]);

Expand All @@ -159,7 +196,8 @@ module('RequestBuilder', function () {
`token 2: ReplaceLarge token can only be used when value exceeds ${CHAR_LIMIT} character limit`,
'token 3: Missing properties for secret token: "path"',
'token 4: Missing properties for hmac token: "name", HMAC algorithm is invalid, HMAC secret name not provided, HMAC encoding is invalid',
'token 5: SHA1 encoding is invalid, Invalid secret token passed into SHA1 tokens array',
'token 5: Missing properties for rsa token: "name", RSA algorithm is invalid, RSA secret name not provided, RSA encoding is invalid',
'token 6: SHA1 encoding is invalid, Invalid secret token passed into SHA1 tokens array',
];

assert.throws(function () {
Expand Down
83 changes: 83 additions & 0 deletions test/token-builder/types-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
ReplaceLargeToken,
SecretToken,
HmacToken,
RsaToken,
Sha1Token,
CHAR_LIMIT,
} from '../../src/token-builder/types';
Expand Down Expand Up @@ -328,6 +329,88 @@ module('HmacToken', function () {
});
});

module('RsaToken', function () {
test('can be instantiated with all options', (assert) => {
const rsaOptions = {
name: 'rsa_sig',
cacheOverride: 'xyz',
skipCache: true,
options: {
stringToSign: 'application/json\nGET\n',
algorithm: 'sha1',
secretName: 'watson',
encoding: 'hex',
},
};

const tokenModel = new RsaToken(rsaOptions);

const expectedJson = {
name: 'rsa_sig',
type: 'rsa',
cacheOverride: 'xyz',
skipCache: true,
options: {
stringToSign: 'application/json\nGET\n',
algorithm: 'sha1',
secretName: 'watson',
encoding: 'hex',
},
};

assert.deepEqual(tokenModel.toJSON(), expectedJson);
});

test('gets instantiated with default options', (assert) => {
const rsaOptions = {
name: 'rsa_sig',
options: {
stringToSign: 'application/json\nGET\n',
algorithm: 'sha1',
secretName: 'watson',
encoding: 'hex',
},
};

const tokenModel = new RsaToken(rsaOptions);

const expectedJson = {
name: 'rsa_sig',
type: 'rsa',
cacheOverride: null,
skipCache: false,
options: {
stringToSign: 'application/json\nGET\n',
algorithm: 'sha1',
secretName: 'watson',
encoding: 'hex',
},
};

assert.deepEqual(tokenModel.toJSON(), expectedJson);
});

test('will include an error if instantiated with missing options', (assert) => {
const rsaOptions = {
name: 'rsa_sig',
options: {
stringToSign: 'application/json\nGET\n',
algorithm: 'invalid',
encoding: 'neo',
},
};

const tokenModel = new RsaToken(rsaOptions);

const expectedErrors = [
'RSA algorithm is invalid',
'RSA secret name not provided',
'RSA encoding is invalid',
];
assert.deepEqual(tokenModel.errors, expectedErrors);
});
});

module('Sha1Token', function () {
test('can be instantiated with all options', (assert) => {
const tokens = [{ name: 'secureValue', type: 'secret', path: 'mySecretPath' }];
Expand Down

0 comments on commit 8c95e48

Please sign in to comment.