UNPKG

@investorid/identity-sdk

Version:
459 lines 20.4 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const axios_1 = __importDefault(require("axios")); const utils_1 = require("ethers/utils"); const Config_1 = require("../core/Config"); const Claim_interface_1 = require("./Claim.interface"); const SignerModule_1 = require("../core/SignerModule"); const ethers_1 = require("ethers"); const Utils_1 = require("../core/utils/Utils"); class Claim { /** * Standardize a Claim Issuer provider response for a claim. * Even if the standard is to return camelCase properties, some may return snake_case. * @param data */ static standardizeProviderResponse(data) { if (!data) { return {}; } if (data._id) { data.id = data._id; } if (data.issuance_date) { data.issuanceDate = data.issuance_date; } if (data.issuanceDate) { data.issuanceDate = new Date(data.issuanceDate); } if (data.emission_date) { data.emissionDate = data.emission_date; } if (data.emissionDate) { data.emissionDate = new Date(data.emissionDate); } if (data.public_data) { data.publicData = data.public_data; } if (!data.publicData) { data.publicData = {}; } if (data.private_data) { data.privateData = data.private_data; } if (!data.privateData) { data.privateData = {}; } data.type = Claim_interface_1.ClaimType[data.type.toUpperCase()] || data.type; return data; } /** * Generate the hash of a claim using the provided data. * @param type * @param emissionDate * @param publicData * @param privateData */ static generateHash(type, emissionDate, publicData, privateData) { if (publicData && privateData && emissionDate && type) { return utils_1.sha256(utils_1.toUtf8Bytes(JSON.stringify({ type, emissionDate, privateData, publicData }))); } else { throw new Error("Can't generate the Claim Hash because some data is missing."); } } static generateBlockchainHash(address, topic, data) { return utils_1.solidityKeccak256(['address', 'uint256', 'bytes'], [address, topic, data]); } /** * Instantiate a new claim from an URI. * @param uri */ static createFromURI(uri) { return __awaiter(this, void 0, void 0, function* () { Utils_1.guardSecuredURI(uri); const claimData = yield axios_1.default({ method: 'GET', baseURL: uri, }).then(res => Claim.standardizeProviderResponse(res.data)).catch(err => { if (err.response && err.response.status === 404) { return null; } throw new Error('Could not retrieve claim from Provider.'); }); return new Claim(Object.assign({}, (claimData || {}), { uri })); }); } /** * Generate the blockchain hash of a claim and signs it with the provided signer or the default signer of the SDK. * @param type * @param address * @param data * @param [signer] * @returns Claim signature. */ static sign(type, address, data, signer) { return __awaiter(this, void 0, void 0, function* () { let _signer = signer; if (!_signer) { const defaultSigner = Config_1.getProvider(); if (!ethers_1.Signer.isSigner(defaultSigner)) { throw new Config_1.InvalidProviderError('A signer is required to generate a claim signature.'); } _signer = new SignerModule_1.SignerModule(defaultSigner); } if (!SignerModule_1.SignerModule.isSignerModule(_signer)) { throw new Config_1.InvalidProviderError('A signer is required to generate a claim signature.'); } if (!Utils_1.isHexString(data)) { throw new Error('Claim data to sign must be a valid hex string.'); } const claimHash = utils_1.solidityKeccak256(['address', 'uint256', 'bytes'], [address, type, data]); return _signer.signMessage(utils_1.arrayify(claimHash)); }); } /** * Verify the signature of a claim. * The standard signature of a claim is the keccak256 hash of (identityAddress, type, data) prefixed and signed. * The data argument is exactly the content of the data claim field stored in blockchain (caution to hex padding). * Data is expected to be an hexString. * Using the IdentitySDK, call `IdentitySDK.utils.toHex('data')`. * Using web3utils, call `web3utils.asciiToHex('data)`. * Using ethersjs, call `Ethers.utils.hexlify(Ethers.utils.toUtf8Bytes('data'))` * @param signingKey * @param type * @param address Address of identity contract. * @param data * @param signature */ static verifySignature(signingKey, type, address, data, signature) { return __awaiter(this, void 0, void 0, function* () { if (!Utils_1.isHexString(data)) { throw new Error('Claim data to sign must be a valid hex string.'); } const hash = utils_1.solidityKeccak256(['address', 'uint256', 'bytes'], [address, type, data]); try { const signerAddress = yield utils_1.verifyMessage(utils_1.arrayify(hash), signature); return signerAddress === signingKey; } catch (err) { return false; } }); } /** * Create a new Claim Object from a ClaimData (got from BlockChain Identity Contract). * Use #.createFromURI() to fetch from an URI. * @param claim */ constructor(claim) { if (claim) { Object.assign(this, claim); } } /** * Complete an AccessGrant challenge to validate a PERSISTENT grant or obtain an access token with an IMMEDIATE grant. * @param accessGrant * @param [signer] Signer to sign the challenge if signature is not provided in accessGrant. */ completeAccessChallenge(accessGrant, signer) { return __awaiter(this, void 0, void 0, function* () { if (accessGrant.signature && signer) { throw new Error('A signer is not expected when the accessGrant signature is provided.'); } else if (!accessGrant.signature) { if (!signer) { throw new Error('A signer is expected or the accessGrant signature must be provided.'); } if (!signer || !signer.signMessage || !signer.getPublicKey) { throw new Error('Provided signer has no .signMessage(message: string) or .getPublicKey() function.'); } try { accessGrant.signature = yield signer.signMessage(accessGrant.challenge); } catch (err) { throw new Error('Provided signer .signMessage(message: string) function threw an error when called.'); } if (!accessGrant.signature) { throw new Error('Provided signer does not expose a .signMessage(message: string) function which returns signature: string.'); } } const result = yield axios_1.default({ method: 'POST', baseURL: accessGrant.uri, url: '/validations', data: { signature: accessGrant.signature, }, }).then(res => res.data); if (!result.access) { throw new Error('Could not validate the access grant request.'); } if (accessGrant.type === Claim_interface_1.AccessGrantType.PERSISTENT) { return Object.assign({}, accessGrant, { status: Claim_interface_1.AccessGrantStatus.CONFIRMED }); } else if (accessGrant.type === Claim_interface_1.AccessGrantType.IMMEDIATE) { if (!result.access_token) { throw new Error('Provided response is not standard. Expecting: { access: true, access_token: string }.'); } return { access: result.access, accessToken: result.access_token, }; } else { throw new Error('Unknown accessGrant Type.'); } }); } /** * Retrieve public data of the claim. * Will fetch a GET on the Claim URI, with ?private_data=true&access_token=<...>. * Use the .requestAccessToken() method to get an Access Token, and .requestAccess() to request access to the claim data. * @param accessToken */ getPrivateData(accessToken) { return __awaiter(this, void 0, void 0, function* () { Utils_1.guardSecuredURI(this.uri); if (this.privateData) { return this.privateData; } const result = yield axios_1.default({ method: 'GET', baseURL: this.uri, params: { private_data: true, access_token: accessToken, }, }).then(res => Claim.standardizeProviderResponse(res.data)).catch((err) => { if (err.response && err.response.status === 404) { return {}; } throw new Error('Could not retrieve claim from Provider.'); }); if (result.privateData) { const hash = this.generateHash(result.type, result.emissionDate, result.publicData || {}, result.privateData || {}); if (hash !== this.data) { throw new Error('Claim data retrieved did not match the hash in the block chain claim data field.'); } } this.issuanceDate = result.issuanceDate; this.emissionDate = result.emissionDate; this.status = result.status; this.privateData = result.privateData; this.publicData = result.publicData; return this.privateData || {}; }); } /** * Retrieve public data of the claim. * Will fetch a GET on the Claim URI. */ getPublicData() { return __awaiter(this, void 0, void 0, function* () { Utils_1.guardSecuredURI(this.uri); if (this.publicData) { return this.publicData; } const result = yield axios_1.default({ method: 'GET', baseURL: this.uri, }).then(res => Claim.standardizeProviderResponse(res.data)).catch(err => { if (err.response && err.response.status === 404) { return null; } throw new Error('Could not retrieve claim from Provider.'); }); if (result) { this.issuanceDate = result.issuanceDate ? new Date(result.issuanceDate) : undefined; this.emissionDate = result.emissionDate ? new Date(result.emissionDate) : undefined; this.status = result.status; this.publicData = result.publicData; } if (!this.publicData) { this.publicData = {}; } return this.publicData; }); } /** * Generate the hash of the claim data if it was populated with private data. * If all data are not provided as arguments, data fromm the Claim object will be used. * Note that to verify a claim complete data, you will obviously need to have access to its private data. * Use the .populate() method to fetch all the public and private data if available. * @param type * @param emissionDate * @param publicData * @param privateData */ generateHash(type, emissionDate, publicData, privateData) { if (publicData && privateData && emissionDate && type) { return Claim.generateHash(type, emissionDate, publicData, privateData); } else { if (!this.publicData || !this.privateData || !this.emissionDate) { throw new Error("Can't generate the Claim Hash because some data is missing. Call .populate() first to retrieve data from the Claim Issuer."); } return this.generateHash(this.type, this.emissionDate, this.publicData, this.privateData); } } /** * Request access to a Claim private data. * If the signer already possess a persistent Access Grant, this should return the existing access grant to request access token with. * To require a PERSISTENT access grant, the signer is required. It must expose the getPublicKey() function. * @param accessType * @param signer Required if access grant type is PERSISTENT. */ requestAccess(accessType = Claim_interface_1.AccessGrantType.IMMEDIATE, signer) { return __awaiter(this, void 0, void 0, function* () { Utils_1.guardSecuredURI(this.uri); let payload = {}; if (accessType === Claim_interface_1.AccessGrantType.PERSISTENT) { if (!signer || !signer.getPublicKey) { throw new Error('Provided signer does not expose a .getPublicKey() function which returns { key: string: type: string, signingMethod: string }.'); } let publicKey; try { publicKey = yield signer.getPublicKey(); } catch (err) { throw new Error('Provided signer .getPublicKey() function threw an error when called.'); } if (!publicKey) { throw new Error('Provided signer does not expose a .getPublicKey() function which returns { key: string: type: string, signingMethod: string }.'); } payload.key = publicKey.key; payload.key_type = publicKey.type; payload.key_signing_method = publicKey.signingMethod; } else if (accessType === Claim_interface_1.AccessGrantType.IMMEDIATE) { } else { throw new Error('Invalid AccessGrantType requested.'); } const result = yield axios_1.default({ method: 'POST', baseURL: this.uri, url: '/access-requests', }).then(res => res.data); if (!result.address || !result.challenge || !result.claim || !result.date || !result.status || !result.type || !result.uri || (!result.id && !result._id)) { throw new Error('Provider response for AccessGrant is not a standard response.'); } return { address: result.address, challenge: result.challenge, claimId: result.claim, date: new Date(result.date), status: Claim_interface_1.AccessGrantStatus[result.status] || result.status, type: Claim_interface_1.AccessGrantType[result.type] || result.type, uri: result.uri, id: result.id || result._id, key: result.key, keyType: result.key_type, keySigningMethod: result.key_signing_method, }; }); } /** * Use a confirmed AccessGrant to obtain an AccessToken. * @param accessGrant * @param signer */ requestAccessToken(accessGrant, signer) { return __awaiter(this, void 0, void 0, function* () { if (accessGrant.type !== Claim_interface_1.AccessGrantType.PERSISTENT) { throw new Error('accessGrant type is not PERSISTENT.'); } if (accessGrant.status !== Claim_interface_1.AccessGrantStatus.CONFIRMED) { throw new Error("accessGrant status is not CONFIRMED. It can't be used to obtain an AccessToken."); } if (!signer || !signer.signMessage || !signer.getPublicKey) { throw new Error('Provided signer has no .signMessage(message: string) and .getPublicKey() function.'); } let publicKey; try { publicKey = yield signer.getPublicKey(); } catch (err) { throw new Error('Provided signer .getPublicKey() function threw an error when called.'); } if (publicKey.key !== accessGrant.key || publicKey.type !== accessGrant.keyType || publicKey.signingMethod !== accessGrant.keySigningMethod) { throw new Error('Provided signed PublicKey is not the one related to this AccessGrant.'); } let signature; try { signature = yield signer.signMessage(accessGrant.challenge); } catch (err) { throw new Error('Provided signer .signMessage(message: string) function threw an error when called.'); } if (!signature) { throw new Error('Provided signer does not expose a .signMessage(message: string) function which returns signature: string.'); } const result = yield axios_1.default({ method: 'POST', baseURL: accessGrant.uri, url: '/validations', data: { signature, }, }).then(res => res.data); if (!result.access || !result.access_token) { throw new Error('Could not request the token.'); } return { access: result.access, accessToken: result.access_token, }; }); } /** * Populate Claim Object with public and eventually private data. * Getting the private data requires an accessToken. * @param accessToken */ populate(accessToken) { return __awaiter(this, void 0, void 0, function* () { if (accessToken) { yield this.getPrivateData(accessToken); } else { yield this.getPublicData(); } }); } /** * Sign the claim. * It will update the signature property of the claim. The signature can only be generated for a claim that as a type, an address and data. * @param signer Signer module to use. If null, use default signer of SDK. */ sign(signer) { return __awaiter(this, void 0, void 0, function* () { if (!this.type) { throw new Error('Claim has no type defined, thus it cannot be signed.'); } if (!this.address) { throw new Error('Claim has no address defined, thus it cannot be signed.'); } if (!this.data) { throw new Error('Claim has no data defined, thus it cannot be signed.'); } const signature = yield Claim.sign(this.type, this.address, this.data, signer); this.signature = signature; return signature; }); } } exports.Claim = Claim; //# sourceMappingURL=Claim.js.map