Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More β†’
Socket
Sign inDemoInstall
Socket

node-my-info-sg

Package Overview
Dependencies
Maintainers
1
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

node-my-info-sg - npm Package Compare versions

Comparing version 1.0.5 to 1.1.0

.babelrc

291

lib/client.js
const _ = require('lodash');
const Promise = require('bluebird');
const crypto = require("crypto");
const querystring = require('querystring');
const restClient = require('superagent-bluebird-promise');
const crypto = require('crypto');
const superagent = require('superagent');
const securityHelper = require('./security');

@@ -10,5 +8,5 @@

constructor(options) {
this._authApiUrl = options.baseUrl + '/com/v3/authorise';
this._personApiUrl = options.baseUrl + '/com/v3/person';
this._tokenApiUrl = options.baseUrl + '/com/v3/token';
this._authApiUrl = `${options.baseUrl}/com/v3/authorise`;
this._personApiUrl = `${options.baseUrl}/com/v3/person`;
this._tokenApiUrl = `${options.baseUrl}/com/v3/token`;
this._authLevel = options.authLevel;

@@ -21,232 +19,131 @@ this._clientId = options.clientId;

}
getAuthoriseUrl(purpose, attributes) {
const state = crypto.randomBytes(16).toString("hex");
var authoriseUrl = this._authApiUrl + "?client_id=" + this._clientId
+ "&attributes="+ attributes.join(',')
+ "&purpose=" + purpose
+ "&state=" + state
+ "&redirect_uri=" + this._redirectUrl;
return { authoriseUrl, state } ;
const state = crypto.randomBytes(16).toString('hex');
const authoriseUrl = `${this._authApiUrl}\
?client_id=${this._clientId}\
&attributes=${attributes.join(',')}\
&purpose=${purpose}\
&state=${state}\
&redirect_uri=${this._redirectUrl}`;
return { authoriseUrl, state };
}
getToken(code) {
var self = this;
return new Promise(function (resolve, reject) {
var _authLevel = self._authLevel;
var _clientId = self._clientId;
var _clientSecret = self._clientSecret;
var _privateKeyPath = self._privateKeyPath;
var _redirectUrl = self._redirectUrl;
var _tokenApiUrl = self._tokenApiUrl;
var cacheCtl = "no-cache";
var contentType = "application/x-www-form-urlencoded";
var method = "POST";
// assemble params for Token API
var strParams = "grant_type=authorization_code" +
"&code=" + code +
"&redirect_uri=" + _redirectUrl +
"&client_id=" + _clientId +
"&client_secret=" + _clientSecret;
var params = querystring.parse(strParams);
async getToken(code) {
const {
_authLevel, _clientId, _clientSecret, _privateKeyPath, _redirectUrl, _tokenApiUrl,
} = this;
if (_authLevel !== 'L0' && _authLevel !== 'L2') throw new Error('UNKNOWN AUTH LEVEL');
// assemble headers for Token API
var strHeaders = "Content-Type=" + contentType + "&Cache-Control=" + cacheCtl;
var headers = querystring.parse(strHeaders);
const params = {
grant_type: 'authorization_code',
redirect_uri: _redirectUrl,
client_id: _clientId,
client_secret: _clientSecret,
code,
};
// Add Authorisation headers for connecting to API Gateway
var authHeaders = null;
if (_authLevel == "L0") {
// No headers
} else if (_authLevel == "L2") {
authHeaders = securityHelper.generateAuthorizationHeader(
_tokenApiUrl,
params,
method,
contentType,
_authLevel,
_clientId,
_privateKeyPath,
_clientSecret
);
} else {
throw new Error("Unknown Auth Level");
}
const headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Cache-Control': 'no-cache',
};
if (!_.isEmpty(authHeaders)) {
_.set(headers, "Authorization", authHeaders);
}
if (_authLevel === 'L2') {
const authHeaders = securityHelper.generateAuthorizationHeader(
_tokenApiUrl,
params,
'POST',
'application/x-www-form-urlencoded',
_authLevel,
_clientId,
_privateKeyPath,
_clientSecret,
);
_.set(headers, 'Authorization', authHeaders);
}
var request = restClient.post(_tokenApiUrl);
const response = await superagent.post(_tokenApiUrl)
.set(headers)
.send(params)
.buffer(true);
// Set headers
if (!_.isUndefined(headers) && !_.isEmpty(headers))
request.set(headers);
return { accessToken: _.get(response, 'body.access_token') };
}
// Set Params
if (!_.isUndefined(params) && !_.isEmpty(params))
request.send(params);
async getPerson(accessToken, attributes) {
const {
_authLevel, _privateKeyPath, _publicCertPath,
} = this;
request
.buffer(true)
.end(function(callErr, callRes) {
if (callErr) {
// ERROR
reject(callErr);
} else {
// SUCCESSFUL
var data = {
body: callRes.body,
text: callRes.text
};
if (_authLevel !== 'L0' && _authLevel !== 'L2') throw new Error('UNKNOWN AUTH LEVEL');
var accessToken = data.body.access_token;
if (accessToken == undefined || accessToken == null) {
reject(new Error('ACCESS TOKEN NOT FOUND'));
}
// validate and decode token to get UINFIN
const decoded = securityHelper.verifyJWS(accessToken, _publicCertPath);
if (!decoded) throw new Error('INVALID TOKEN');
resolve({ accessToken });
}
});
});
};
getPerson(accessToken, attributes) {
var self = this;
return new Promise(function(resolve, reject) {
var _authLevel = self._authLevel;
var _privateKeyPath = self._privateKeyPath;
var _publicCertPath = self._publicCertPath;
const uinfin = decoded.sub;
if (!uinfin) throw new Error('UINFIN NOT FOUND');
// validate and decode token to get UINFIN
var decoded = securityHelper.verifyJWS(accessToken, _publicCertPath);
if (decoded == undefined || decoded == null) {
reject(new Error("INVALID TOKEN"));
}
const response = await this._createPersonRequest(uinfin, accessToken, attributes);
const responseText = response.text;
if (_authLevel === 'L0') {
const personData = JSON.parse(responseText);
return { person: personData };
}
var uinfin = decoded.sub;
if (uinfin == undefined || uinfin == null) {
reject(new Error("UINFIN NOT FOUND"));
}
// _authLevel === 'L2'
const [header, encryptedKey, iv, ciphertext, tag] = responseText.split('.');
// **** CALL PERSON API ****
var request = self._createPersonRequest(uinfin, accessToken, attributes.join(','));
const personDataJWS = await securityHelper.decryptJWE(header, encryptedKey, iv, ciphertext, tag, _privateKeyPath);
if (!personDataJWS) throw new Error('INVALID DATA OR SIGNATURE FOR PERSON DATA');
// Invoke asynchronous call
request
.buffer(true)
.end(function(callErr, callRes) {
if (callErr) {
reject(callErr);
} else {
// SUCCESSFUL
var data = {
body: callRes.body,
text: callRes.text
};
var personData = data.text;
if (personData == undefined || personData == null) {
reject(new Error("PERSON DATA NOT FOUND"));
} else {
const decodedPersonData = securityHelper.verifyJWS(personDataJWS, _publicCertPath);
if (!decodedPersonData) throw new Error('INVALID DATA OR SIGNATURE FOR PERSON DATA');
if (_authLevel == "L0") {
personData = JSON.parse(personData);
// personData = securityHelper.verifyJWS(personData, _publicCertPath);
return { person: decodedPersonData };
}
if (personData == undefined || personData == null) {
reject(new Error("INVALID DATA OR SIGNATURE FOR PERSON DATA"));
}
_createPersonRequest(uinfin, validToken, attributes) {
const {
_authLevel, _clientId, _clientSecret, _personApiUrl, _privateKeyPath,
} = this;
// successful. return data back to frontend
resolve({ person: personData });
const url = `${_personApiUrl}/${uinfin}/`;
}
else if(_authLevel == "L2"){
var jweParts = personData.split("."); // header.encryptedKey.iv.ciphertext.tag
securityHelper.decryptJWE(jweParts[0], jweParts[1], jweParts[2], jweParts[3], jweParts[4], _privateKeyPath)
.then(personDataJWS => {
if (personDataJWS == undefined || personDataJWS == null) {
reject(new Error("INVALID DATA OR SIGNATURE FOR PERSON DATA"));
}
var decodedPersonData = securityHelper.verifyJWS(personDataJWS, _publicCertPath);
if (decodedPersonData == undefined || decodedPersonData == null) {
reject(new Error("INVALID DATA OR SIGNATURE FOR PERSON DATA"));
}
// successful. return data back to frontend
resolve({ person: decodedPersonData });
})
}
else {
reject(new Error("Unknown Auth Level"));
}
} // end else
}
}); //end asynchronous call
});
}
_createPersonRequest(uinfin, validToken, attributes) {
var _attributes = attributes;
var _authLevel = this._authLevel;
var _clientId = this._clientId;
var _clientSecret = this._clientSecret;
var _personApiUrl = this._personApiUrl;
var _privateKeyPath = this._privateKeyPath;
var url = _personApiUrl + "/" + uinfin + "/";
var cacheCtl = "no-cache";
var method = "GET";
// assemble params for Person API
var strParams = "client_id=" + _clientId +
"&attributes=" + _attributes;
const params = {
client_id: _clientId,
attributes: attributes.join(','),
};
var params = querystring.parse(strParams);
// assemble headers for Person API
var strHeaders = "Cache-Control=" + cacheCtl;
var headers = querystring.parse(strHeaders);
const headers = { 'Cache-Control': 'no-cache' };
// Add Authorisation headers for connecting to API Gateway
var authHeaders = securityHelper.generateAuthorizationHeader(
const authHeaders = securityHelper.generateAuthorizationHeader(
url,
params,
method,
"", // no content type needed for GET
'GET',
'', // no content type needed for GET
_authLevel,
_clientId,
_privateKeyPath,
_clientSecret
_clientSecret,
);
// NOTE: include access token in Authorization header as "Bearer " (with space behind)
// NOTE: include access token in Authorization header as 'Bearer ' (with space behind)
if (!_.isEmpty(authHeaders)) {
_.set(headers, "Authorization", authHeaders + ",Bearer " + validToken);
_.set(headers, 'Authorization', `${authHeaders},Bearer ${validToken}`);
} else {
_.set(headers, "Authorization", "Bearer " + validToken);
_.set(headers, 'Authorization', `Bearer ${validToken}`);
}
// invoke person API
var request = restClient.get(url);
// Set headers
if (!_.isUndefined(headers) && !_.isEmpty(headers))
request.set(headers);
// Set Params
if (!_.isUndefined(params) && !_.isEmpty(params))
request.query(params);
return request;
return superagent.get(url)
.set(headers)
.query(params)
.buffer(true);
}

@@ -253,0 +150,0 @@ }

@@ -6,3 +6,2 @@ const _ = require('lodash');

const jwt = require('jsonwebtoken');
const nonce = require('nonce')();
const qs = require('querystring');

@@ -14,5 +13,3 @@

function sortJSON(json) {
if (_.isNil(json)) {
return json;
}
if (_.isNil(json)) return json;

@@ -23,4 +20,4 @@ const newJSON = {};

for (key in keys) {
newJSON[keys[key]] = json[keys[key]];
for (let i = 0; i < keys.length; i += 1) {
newJSON[keys[i]] = json[keys[i]];
}

@@ -42,6 +39,7 @@

function generateSHA256withRSAHeader(url, _params, method, strContentType, appId, keyCertContent, keyCertPassphrase) {
const nonceValue = nonce();
const nonceValue = crypto.randomBytes(16).toString('hex');
const timestamp = (new Date()).getTime();
let params = _params;
// A) Construct the Authorisation Token

@@ -60,2 +58,3 @@ const defaultApexHeaders = {

// B) Forming the Signature Base String

@@ -72,3 +71,3 @@

// iii) concatenate request elements
const baseString = method.toUpperCase() + "&" + url + "&" + baseParamsStr;
const baseString = `${method.toUpperCase()}&${url}&${baseParamsStr}`;

@@ -81,3 +80,3 @@

if (!_.isUndefined(keyCertPassphrase) && !_.isEmpty(keyCertPassphrase)) _.set(signWith, "passphrase", keyCertPassphrase);
if (!_.isUndefined(keyCertPassphrase) && !_.isEmpty(keyCertPassphrase)) _.set(signWith, 'passphrase', keyCertPassphrase);

@@ -89,11 +88,9 @@ // Load pem file containing the x509 cert & private key & sign the base string with it.

// D) Assembling the Header
const strApexHeader = "PKI_SIGN timestamp=\"" + timestamp +
"\",nonce=\"" + nonceValue +
"\",app_id=\"" + appId +
"\",signature_method=\"RS256\"" +
",signature=\"" + signature +
"\"";
const strApexHeader = `\
PKI_SIGN timestamp="${timestamp}",\
nonce="${nonceValue}",\
app_id="${appId}",\
signature_method="RS256",\
signature="${signature}"`;

@@ -111,12 +108,10 @@ return strApexHeader;

*/
security.generateAuthorizationHeader = function(url, params, method, strContentType, authType, appId, keyCertContent, passphrase) {
if (authType == "L2") {
return generateSHA256withRSAHeader(url, params, method, strContentType, appId, keyCertContent, passphrase);
}
else {
return "";
}
security.generateAuthorizationHeader = function generateAuthorizationHeader(
url, params, method, strContentType,
authType, appId, keyCertContent, passphrase,
) {
if (authType !== 'L2') return '';
return generateSHA256withRSAHeader(url, params, method, strContentType, appId, keyCertContent, passphrase);
};
}
// Verify & Decode JWS or JWT

@@ -129,19 +124,17 @@ security.verifyJWS = function verifyJWS(jws, publicCert) {

algorithms: ['RS256'],
ignoreNotBefore: true
ignoreNotBefore: true,
});
return decoded;
} catch (error) {
throw ("Error with verifying and decoding JWE");
throw new Error('ERROR WITH VERIFYING AND DECODING JWE');
}
}
};
// Decrypt JWE using private key
security.decryptJWE = function decryptJWE(header, encryptedKey, iv, cipherText, tag, privateKey) {
return new Promise((resolve, reject) => {
security.decryptJWE = async function decryptJWE(header, encryptedKey, iv, cipherText, tag, privateKey) {
try {
const keystore = jose.JWK.createKeyStore();
const data = {
type: "compact",
type: 'compact',
ciphertext: cipherText,

@@ -152,23 +145,13 @@ protected: header,

iv,
header: JSON.parse(jose.util.base64url.decode(header).toString())
header: JSON.parse(jose.util.base64url.decode(header).toString()),
};
keystore.add(fs.readFileSync(privateKey, 'utf8'), "pem")
.then(function(jweKey) {
// {result} is a jose.JWK.Key
jose.JWE.createDecrypt(jweKey)
.decrypt(data)
.then(function(result) {
resolve(JSON.parse(result.payload.toString()));
})
.catch(function(error) {
reject(error);
});
});
})
.catch (error => {
throw "Error with decrypting JWE";
})
}
const jweKey = await keystore.add(fs.readFileSync(privateKey, 'utf8'), 'pem');
const result = await jose.JWE.createDecrypt(jweKey).decrypt(data);
return JSON.parse(result.payload.toString());
} catch (error) {
throw new Error('ERROR WITH DECRYPTING JWE');
}
};
module.exports = security;
{
"name": "node-my-info-sg",
"version": "1.0.5",
"version": "1.1.0",
"description": "",
"main": "lib/client.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "jest",
"test:coverage": "jest --coverage --collectCoverageFrom='[\"**/src/**/*.js\", \"!**/examples/**\",\"!**/components/**\",\"!**/constants/**\"]'",
"test:watch": "jest --watch",
"lint": "eslint --ext .js ."
},
"author": "",
"author": "StashAway",
"repository": {

@@ -19,10 +22,30 @@ "type": "git",

"dependencies": {
"bluebird": "^3.5.4",
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.11",
"node-jose": "^1.1.3",
"nonce": "^1.0.4",
"superagent": "^5.0.2",
"superagent-bluebird-promise": "^4.2.0"
"superagent": "^5.0.2"
},
"devDependencies": {
"@babel/core": "^7.0.0-0",
"@babel/preset-env": "^7.4.4",
"babel-eslint": "^10.0.1",
"eslint": "^5.16.0",
"eslint-config-airbnb": "^17.1.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jsx-a11y": "^6.1.1",
"eslint-plugin-react": "^7.11.0",
"jest": "^24.8.0",
"jest-watch-typeahead": "^0.3.1",
"puppeteer": "^1.15.0",
"sinon": "^7.3.2"
},
"jest": {
"testMatch": [
"**/?(*.)(spec|test).js?(x)"
],
"watchPlugins": [
"jest-watch-typeahead/filename",
"jest-watch-typeahead/testname"
]
}
}
# node-my-info-sg πŸ‡ΈπŸ‡¬
[![npm version](https://badge.fury.io/js/node-my-info-sg.svg)](https://badge.fury.io/js/node-my-info-sg) [![CircleCI](https://circleci.com/gh/stashaway-engineering/node-my-info-sg.svg?style=svg)](https://circleci.com/gh/stashaway-engineering/node-my-info-sg)

@@ -62,3 +63,10 @@ Small wrapper around Singapore [MyInfo V3 API](https://www.ndi-api.gov.sg/library/trusted-data/myinfo/introduction) for node JS. Wraps the scary-scary 😱 security logic into easy to use APIs

```
## Test
```js
yarn test
```
## Example

@@ -75,3 +83,3 @@

1. Add unit tests and sensible linting rules
1. Pass this repository to the cool government guy, so they can maintain it
1. Add sensible linting rules
1. Pass this repository to the cool government guy, so they can maintain it
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚑️ by Socket Inc