UNPKG

heimdall-tide

Version:

SDK for communicating with a Tide Enclave

309 lines 12.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.RequestEnclave = void 0; const asgard_tide_1 = require("asgard-tide"); const heimdall_1 = require("../heimdall"); const asgard_tide_2 = require("asgard-tide"); class RequestEnclave extends heimdall_1.Heimdall { constructor() { super(...arguments); this.name = "request"; this._windowType = heimdall_1.windowType.Hidden; this.initDone = this.recieve("init done"); } init(data) { if (!data.doken) throw 'Doken not provided'; this.doken = data.doken; let parsedDoken = decodeToken(this.doken); if (parsedDoken["t.uho"]) this.enclaveOrigin = parsedDoken["t.uho"]; // use tidecloak set user home ork from doken this.dokenRefreshCallback = data.dokenRefreshCallback; this.requireReloginCallback = data.requireReloginCallback; this.recieve("hidden enclave").then((data) => this.handleHiddenEnclaveResponse(data)); this.open().then((success) => { if (success) { this.send({ type: "init", message: { doken: this.doken } }); } else { // If injecting iframe fails, try setting it as a popup and opening it this._windowType = heimdall_1.windowType.Popup; this.open().then((success) => { if (success) { this.send({ type: "init", message: { doken: this.doken } }); } else throw 'Error opening all types of Request Enclave'; }); } }); return this; } async handleHiddenEnclaveResponse(msg) { // Below is the session key mismatch flow that was implemented but then it was decided a basic relogin was more elegent // Keeping it though because it is nearly identical to the flow where a tide user delegates a token to another tide user // This would require the second tide user to sign a new delegated token with their current session key // This would be gold in a cvk scenario // if(msg == "session key mismatch" && this._windowType == windowType.Hidden){ // this.initDone = this.recieve("init done"); // await the REOPENED HIDDEN ENCLAVE INIT DONE SIGNAL // // looks like the hidden iframe has not allowed data to be stored on the browser OR the session key is mismatched with whats on the enclave vs doken // // either way we gotta get a doken with the appropriate session key // // Close the hidden enclave // this.close(); // // We're now going to open the request enclave as a popup with the mismatched doken // // The page should recognise the doken is mismatched, generate a new one, then close // this._windowType = windowType.Popup; // // open popup // await this.open(); // // send doken to refresh // this.send({ // type: "init", // message:{ // doken: this.doken // } // }); // // wait for new doken // const resp = await this.recieve("refreshed doken"); // this.doken = resp.doken; // if(this.requireReloginCallback) this.requireReloginCallback(); // // close pop up enclave // this.close(); // // reset page to hidden iframe // this._windowType = windowType.Hidden; // // open hidden iframe // this.open().then((success: boolean) => { // if(success){ // this.send({ // type: "init", // message: { // doken: this.doken // } // }); // }else throw 'Error opening enclave'; // }); // } if (msg == "session key mismatch") { this.close(); this.requireReloginCallback(); // should initiate a full client page reload, killing this } else if (msg == "storage issue") { // Convert hidden enclave into popup this.close(); this._windowType = heimdall_1.windowType.Popup; this.open().then((success) => { if (success) { this.send({ type: "init", message: { doken: this.doken } }); } else { window.alert("There was an issue opening the fallback popup on this page. Please enable popups or let the administrator know about this problem. For more information, visit https://tide.org/browserwindow"); throw "Could not open popup"; } }); } this.recieve("hidden enclave").then((data) => this.handleHiddenEnclaveResponse(data)); } getOrkUrl() { // construct ork url const url = new URL(this.enclaveOrigin); // Set hidden status url.searchParams.set("hidden", this._windowType == heimdall_1.windowType.Hidden ? "true" : "false"); // Set vendor public url.searchParams.set("vendorId", this.vendorId); // Set client origin url.searchParams.set("origin", encodeURIComponent(window.location.origin)); // Set client origin signature (by vendor) url.searchParams.set("originsig", encodeURIComponent(this.signed_client_origin)); // Set voucher url url.searchParams.set("voucherURL", encodeURIComponent(this.voucherURL)); // Set requestsed enclave url.searchParams.set("type", this.name); return url; } checkEnclaveOpen() { if (this.enclaveClosed()) { // Enclave was closed! // We need to reopen the enclave and await the init again this.initDone = this.recieve("init done"); this.open().then((success) => { if (success) { this.send({ type: "init", message: { doken: this.doken } }); } else throw 'Error opening enclave'; }); } } async initializeRequest(request) { // construct request to sign this request's creation const requestToInitialize = asgard_tide_1.BaseTideRequest.decode(request); const requestToInitializeDetails = await requestToInitialize.getRequestInitDetails(); const initRequest = new asgard_tide_1.BaseTideRequest("TideRequestInitialization", "1", "Doken:1", asgard_tide_2.TideMemory.CreateFromArray([ requestToInitializeDetails.creationTime, requestToInitializeDetails.expireTime, requestToInitializeDetails.modelId, requestToInitializeDetails.draftHash ]), new asgard_tide_2.TideMemory()); const creationSig = (await this.execute(initRequest.encode()))[0]; // returns the same request provided except with the policy authorized creation datas included return requestToInitialize.addCreationSignature(requestToInitializeDetails.creationTime, creationSig).encode(); } async execute(data) { this.checkEnclaveOpen(); await this.initDone; const pre_resp = this.recieve("sign request completed"); this.send({ type: "request", message: { flow: "sign", request: data, } }); const resp = await pre_resp; if (!Array.isArray(resp)) throw 'Expecting request completed data to be an array, not' + resp; if (!resp.every((d) => d instanceof Uint8Array)) throw 'Expecting all entries in response to be Uint8Arrays'; return resp; } async decrypt(data) { this.checkEnclaveOpen(); await this.initDone; const pre_resp = this.recieve("decrypt request completed"); this.send({ type: "request", message: { flow: "decrypt", request: data } }); const resp = await pre_resp; if (!Array.isArray(resp)) throw 'Expecting request completed data to be an array, not' + resp; if (!resp.every((d) => d instanceof Uint8Array)) throw 'Expecting all entries in response to be Uint8Arrays'; return resp; } async encrypt(data) { this.checkEnclaveOpen(); await this.initDone; const pre_resp = this.recieve("encrypt request completed"); this.send({ type: "request", message: { flow: "encrypt", request: data } }); const resp = await pre_resp; if (!Array.isArray(resp)) throw 'Expecting request completed data to be an array, not' + resp; if (!resp.every((d) => d instanceof Uint8Array)) throw 'Expecting all entries in response to be Uint8Arrays'; return resp; } async updateDoken(doken) { this.doken = doken; this.send({ type: "doken refresh", message: { doken: this.doken } }); } async onerror(data) { if (typeof data.message === "string") { switch (data.message) { case "expired": if (!this.dokenRefreshCallback) { console.error("[HEIMDALL] Doken on enclave has expired but there is no Doken Refresh Callback registered"); return; } console.log("[HEIMDALL] Refreshing doken"); this.doken = await this.dokenRefreshCallback(); this.send({ type: "doken refresh", message: { doken: this.doken } }); break; default: this.close(); throw new Error("[HEIMDALL] Recieved enclave error: " + data.message); } } } } exports.RequestEnclave = RequestEnclave; function decodeToken(token) { const [header, payload] = token.split("."); if (typeof payload !== "string") { throw new Error("Unable to decode token, payload not found."); } let decoded; try { decoded = base64UrlDecode(payload); } catch (error) { throw new Error("Unable to decode token, payload is not a valid Base64URL value. Error: " + error); } try { return JSON.parse(decoded); } catch (error) { throw new Error("Unable to decode token, payload is not a valid JSON value. Error: " + error); } } function base64UrlDecode(input) { let output = input .replace(/-/g, "+") .replace(/_/g, "/"); switch (output.length % 4) { case 0: break; case 2: output += "=="; break; case 3: output += "="; break; default: throw new Error("Input is not of the correct length."); } try { return b64DecodeUnicode(output); } catch (error) { return atob(output); } } function b64DecodeUnicode(input) { return decodeURIComponent(atob(input).replace(/(.)/g, (m, p) => { let code = p.charCodeAt(0).toString(16).toUpperCase(); if (code.length < 2) { code = "0" + code; } return "%" + code; })); } //# sourceMappingURL=RequestEnclave.js.map