@privapi/client-js
Version:
PRIVAPI NPM CLIENT PACKAGE
315 lines (271 loc) • 8.91 kB
JavaScript
import { v4 as uuidv4 } from 'uuid';
import axios from 'axios';
import crypto from 'crypto';
import aes256 from 'aes256';
const config = {
individual: {
newRequestUrl: '/public/request/new',
getClientPublicKeyUrl: '/public/request/get-public-client-key',
checkStatusUrl: '/public/request/check-status',
},
prod: {
endpointUrl: 'https://api.privapi.io/',
newRequestUrl: 'https://api.privapi.io/public/request/new',
getClientPublicKeyUrl:
'https://api.privapi.io/public/request/get-client-public-key',
checkStatusUrl: 'https://api.privapi.io/public/request/check-status',
},
};
export class PrivapiClient {
constructor(apiKeyId, apiKeySecret, apiUrl) {
this.apiKeyId = apiKeyId;
this.apiKeySecret = apiKeySecret;
if (apiUrl) {
console.log(
'Using individual endpoint (only for testing purposes):',
apiUrl
);
//this.endpointUrl = apiUrl + config.individual.endpointUrl;
this.newRequestUrl = apiUrl + config.individual.newRequestUrl;
this.getClientPublicKeyUrl =
apiUrl + config.individual.getClientPublicKeyUrl;
this.checkStatusUrl = apiUrl + config.individual.checkStatusUrl;
console.log('Endpoint URL:', this.endpointUrl);
console.log('New Request URL:', this.newRequestUrl);
console.log('Get Client Public Key URL:', this.getClientPublicKeyUrl);
console.log('Check Status URL:', this.checkStatusUrl);
} else {
//this.endpointUrl = config.prod.endpointUrl;
this.newRequestUrl = config.prod.newRequestUrl;
this.getClientPublicKeyUrl = config.prod.getClientPublicKeyUrl;
this.checkStatusUrl = config.prod.checkStatusUrl;
}
}
_computeEcdhSecret() {
return new Promise(async (resolve, reject) => {
try {
const endpoint = crypto.createECDH('secp256k1');
endpoint.generateKeys();
const clientPublicKeyBase64 = await this._getClientPublicKey();
// console.log('Client Public Key:', clientPublicKeyBase64);
const endpointSharedKey = endpoint.computeSecret(
clientPublicKeyBase64,
'base64',
'hex'
);
// console.log('Shared Secret:', endpointSharedKey);
resolve({
privateKey: endpoint.getPrivateKey().toString('base64'),
publicKey: endpoint.getPublicKey().toString('base64'),
sharedSecret: endpointSharedKey,
});
} catch (error) {
reject(error);
}
});
}
_ecdhTest() {
const endpoint = crypto.createECDH('secp256k1');
endpoint.generateKeys();
// console.log(
// 'endpoint Private Key:',
// endpoint.getPrivateKey().toString('base64')
// );
// console.log(
// 'endpoint Public Key:',
// endpoint.getPublicKey().toString('base64')
// );
const client = crypto.createECDH('secp256k1');
client.generateKeys();
// console.log(
// 'client Private Key:',
// client.getPrivateKey().toString('base64')
// );
// console.log('client Public Key:', client.getPublicKey().toString('base64'));
const endpointPublicKeyBase64 = endpoint.getPublicKey().toString('base64');
const clientPublicKeyBase64 = client.getPublicKey().toString('base64');
const endpointSharedKey = endpoint.computeSecret(
clientPublicKeyBase64,
'base64',
'hex'
);
const clientSharedKey = client.computeSecret(
endpointPublicKeyBase64,
'base64',
'hex'
);
// console.log(endpointSharedKey === clientSharedKey);
// console.log('endpoint shared Key: ', endpointSharedKey);
// console.log('client shared Key: ', clientSharedKey);
const message = 'this is some random message...';
//Alice will encrypt this message using AES256 and her shared key...
const encrypted = aes256.encrypt(endpointSharedKey, message);
// Now somehow tranfer this message to bob, using your server or something like that...
//Now bob has the message and he can use his shared key to decrypt the message...
const decrypted = aes256.decrypt(clientSharedKey, encrypted);
//Logging in the values...finally...
console.table({ encrypted, decrypted });
// console.log(crypto.getCurves());
}
_createApiToken() {
return new Promise(async (resolve, reject) => {
try {
// 1) Create NONCE
const nonce = uuidv4();
// 2) HASH API KEY SECRET with NONCE & timestamp
const timestamp = new Date().getTime();
// Defining key
// Calling createHash method
const hash = crypto
.createHash('sha256', this.apiKeySecret)
// updating data
.update(this.apiKeySecret + timestamp.toString() + nonce)
// Encoding to be used
.digest('base64');
// Displays output
//console.log(hash);
resolve({
apiKeyId: this.apiKeyId,
hash: hash,
timestamp: timestamp.toString(),
nonce: nonce,
});
} catch (error) {
console.error('Error creating API Token:', error.message);
reject(error.message);
}
});
}
_getClientPublicKey() {
return new Promise(async (resolve, reject) => {
// 3) Send Request with Hash & NONCE & timestamp
// console.log(
// 'Fetching public client key from API: ',
// this.getClientPublicKeyUrl
// );
const requestToken = await this._createApiToken();
console.log('ApiToken:', requestToken);
const data = {
requestToken: requestToken,
};
console.log('Fetching Client Key from: ', this.getClientPublicKeyUrl);
axios
.post(`${this.getClientPublicKeyUrl}`, data)
.then((response) => {
if (response.data) {
resolve(response.data.clientPublicKey);
} else {
reject('Error fetching client public key.');
}
// resolve(response?.data?.clientPublicKey);
})
.catch((error) => {
console.log(
'Error fetching client public key from URL ',
this.getClientPublicKeyUrl,
': ',
error.message
);
reject(error.message);
});
});
}
createEncryptedRequest(
// dataSubjectIdentificationReference,
// requestType,
// products,
// emailOfDataSubject,
// identificationParameters
parameter
) {
return new Promise(async (resolve, reject) => {
try {
if (!parameter) throw new Error('Request parameters are not defined.');
// Test if required parameters have been provided
if (parameter.requestType === undefined)
throw new Error('Request Type not defined.');
if (!parameter.dataSubjectIdentificationReference)
throw new Error(
'Identification Reference (e.g. database id or customer id not defined.'
);
if (!parameter.products)
throw new Error('Products used by user are not defined.');
// Generate endpoint's public key and ECDH shared secret
const secrets = await this._computeEcdhSecret();
const requestToken = await this._createApiToken();
const data = {
// add hash to authenticate at api
requestToken: requestToken,
request: {
//encrypt sensitive data with ECDH shared secret
dataSubjectIdentificationReference: aes256.encrypt(
secrets.sharedSecret,
parameter.dataSubjectIdentificationReference
),
identificationParameters: parameter.identificationParameters,
requestType: parameter.requestType,
// do not encrypt products
// products: aes256.encrypt(
// secrets.sharedSecret,
// JSON.stringify(products)
// ),
products: JSON.stringify(parameter.products),
// origin: aes256.encrypt(secrets.sharedSecret, origin),
key: secrets.publicKey,
keyTest: aes256.encrypt(secrets.sharedSecret, 'OK'), // used in the portal to determine if the decryption key is valid
dataSubjectEmail: parameter.dataSubjectEmail,
oneTimePassword: secrets.privateKey,
},
};
axios
.post(`${this.newRequestUrl}`, data)
.then((response) => {
if (response && response.data) {
resolve({
newRequestId: response.data.newRequestId,
secret: secrets.sharedSecret,
requestType: response.data.requestType,
requestDate: response.data.requestDate,
oneTimePassword: secrets.privateKey,
});
} else {
throw new Error(
'New request could not be resolved after creation.'
);
}
})
.catch((error) => {
// console.log('Axios Error: ', error);
throw error;
});
} catch (error) {
reject(error);
}
});
}
checkRequestStatus(requestId) {
return new Promise(async (resolve, reject) => {
try {
const requestToken = await this._createApiToken();
const data = {
requestToken: requestToken,
externalRequestId: requestId,
};
// console.log('URL:', this.checkStatusUrl);
axios
.post(`${this.checkStatusUrl}`, data)
.then((response) => {
// console.log('Respnse to client-js:', response);
resolve(response.data);
})
.catch((error) => {
console.log('Axios Error: ', error.message);
reject(error.message);
});
} catch (error) {
reject(error.message);
}
});
}
}
export default PrivapiClient;