UNPKG

@privapi/client-js

Version:
315 lines (271 loc) 8.91 kB
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;