Skip to content
Snippets Groups Projects
viamapi-iframe.js 75.6 KiB
Newer Older
Sasha Ilieva's avatar
Sasha Ilieva committed
import "../lib/textencoder.polyfill";
import { parseSMIME, prepareVCardParts } from "../utilities/emailUtilities";
import {
  stringToUtf8ByteArray,
  utf8ByteArrayToString,
  stringToUtf8Base64,
  utf8Base64ToString,
  base64ToByteArray,
Alexey Lunin's avatar
Alexey Lunin committed
} from "../utilities/stringUtilities";
import { extractMessageID } from "../helpers/mailparser";
Alexey Lunin's avatar
Alexey Lunin committed
const Penpal = require("penpal").default;
import {
  createDeviceHash,
  destroyIdentityFromLocalStorage,
  encodeResponse,
Alexey Lunin's avatar
Alexey Lunin committed
  listIdentitiesFromLocalStorage,
Alexey Lunin's avatar
Alexey Lunin committed
} from "../utilities/appUtility";
import { LOGIN_MODES } from "../constants/authentication";
Damyan Mitev's avatar
Damyan Mitev committed
  ImageData,
  createOneTimePassportCertificate,
  createPassportCertificate,
  decryptMessage,
  encryptMessage,
  parseCertificate,
  signEmail,
Alexey Lunin's avatar
Alexey Lunin committed
} from "../utilities/signingUtilities";
import { signPdf } from "../utilities/pdfUtilities";
import CryptoData from "../CryptoData";
import Identity from "../Identity";
import {
  STATUS_DEVICE_REVOKED,
Sasha Ilieva's avatar
Sasha Ilieva committed
  STATUS_USER_NOT_ACTIVATED,
} from "../constants/statuses";
import generateQrCode from "../utilities/generateQrCode";
Sasha Ilieva's avatar
Sasha Ilieva committed
import {
  generateRecoveryKey,
Sasha Ilieva's avatar
Sasha Ilieva committed
  getRecoveryKeyShares,
  checkRecoveryKeyCombine,
Sasha Ilieva's avatar
Sasha Ilieva committed
} from "../utilities/secrets";
Alexey Lunin's avatar
Alexey Lunin committed

const penpalMethods = require("../../temp/penpal-methods").default;
const WopiAPI = require("./wopiapi-iframe");
const CollaboraAPI = require("./collaboraapi-iframe");
const ViamAPI = require("../../temp/viamapi");
Zdravko Iliev's avatar
Zdravko Iliev committed
const identityColors = ["#994392", "#cb0767", "#e51d31", "#ec671b", "#fab610"];

function getNextColor() {
Alexey Lunin's avatar
Alexey Lunin committed
  let colorIndex = localStorage.getItem("colorIndex");
Markin Igor's avatar
Markin Igor committed
  if (colorIndex == null || colorIndex === "") {
Alexey Lunin's avatar
Alexey Lunin committed
    colorIndex = 0;
Zdravko Iliev's avatar
Zdravko Iliev committed
  const color = identityColors[colorIndex];
Markin Igor's avatar
Markin Igor committed
  colorIndex = colorIndex % identityColors.length;
Markin Igor's avatar
Markin Igor committed
  localStorage.setItem("colorIndex", colorIndex);
Alexey Lunin's avatar
Alexey Lunin committed
  return color;
}

function setKeyForUUID(uuid, key) {
Zdravko Iliev's avatar
Zdravko Iliev committed
  const storedIdentityForUuid = localStorage.getItem("keyperuuid/" + uuid);
Alexey Lunin's avatar
Alexey Lunin committed
  if (
    storedIdentityForUuid !== key &&
    storedIdentityForUuid != null &&
    storedIdentityForUuid !== ""
  ) {
    destroyIdentityFromLocalStorage(storedIdentityForUuid);
Alexey Lunin's avatar
Alexey Lunin committed
  localStorage.setItem("keyperuuid/" + uuid, key);
}

function getColorForIdentity(key) {
Alexey Lunin's avatar
Alexey Lunin committed
  let storedColor = localStorage.getItem("colors/" + key);
Alexey Lunin's avatar
Alexey Lunin committed
  if (storedColor == null || storedColor === "") {
Markin Igor's avatar
Markin Igor committed
    storedColor = getNextColor();
Alexey Lunin's avatar
Alexey Lunin committed
    localStorage.setItem("colors/" + key, storedColor);
Alexey Lunin's avatar
Alexey Lunin committed
  return storedColor;
}

function setIdentityInLocalStorage(identityToStore, extendKey = true) {
Alexey Lunin's avatar
Alexey Lunin committed
  let pinCode = identityToStore.pinCode;
  const serializedIdentity = JSON.stringify(identityToStore);
  const key = identityToStore.authentication.publicKey;

Alexey Lunin's avatar
Alexey Lunin committed
  if (pinCode == null || pinCode === "") {
    pinCode = getPincode(key);
Alexey Lunin's avatar
Alexey Lunin committed
  if (pinCode == null || pinCode === "") {
Alexey Lunin's avatar
Alexey Lunin committed
  return encryptMessage(serializedIdentity, pinCode, "identity").then(
Alexey Lunin's avatar
Alexey Lunin committed
      let success = true;
Alexey Lunin's avatar
Alexey Lunin committed
      if (extendKey === true) {
        success = extendPinCodeTtl(key, pinCode);
      }
      if (success === true) {
        localStorage.setItem(key, encryptedIdentity);
Zdravko Iliev's avatar
Zdravko Iliev committed
        const serializedIdentitiesList = localStorage.getItem("identities");
        const identities = JSON.parse(serializedIdentitiesList);
Alexey Lunin's avatar
Alexey Lunin committed
        identities[key] = true;
Alexey Lunin's avatar
Alexey Lunin committed
        localStorage.setItem("identities", JSON.stringify(identities));
      } else {
        console.log("Can not extend pincode ttl");
      }
Alexey Lunin's avatar
Alexey Lunin committed
  );
}

function getProfileData(identity) {
  return new Penpal.Promise(executeResultUpper => {
Alexey Lunin's avatar
Alexey Lunin committed
    executeRestfulFunction(
      "private",
      viamApi,
      viamApi.identityGetIdentityProfileData,
      null
Alexey Lunin's avatar
Alexey Lunin committed
      if (executeResult.code === "200") {
Zdravko Iliev's avatar
Zdravko Iliev committed
        const listItem = {};
Alexey Lunin's avatar
Alexey Lunin committed
        listItem.identityColor = getColorForIdentity(
          identity.authentication.publicKey
        );
Markin Igor's avatar
Markin Igor committed
        listItem.initials = executeResult.data.initials;
Alexey Lunin's avatar
Alexey Lunin committed
        if (listItem.initials === null || listItem.initials === "") {
          listItem.initials = "JD";
        }
Alexey Lunin's avatar
Alexey Lunin committed
        localStorage.setItem(
          "profiles/" + identity.authentication.publicKey,
          JSON.stringify(listItem)
        );
        executeResultUpper(listItem);
      } else {
Alexey Lunin's avatar
Alexey Lunin committed
        executeResultUpper({});
async function getIdentityFromLocalStorage(key, pinCode, extendTtl = true) {
  const encryptedIdentity = localStorage.getItem(key);
Markin Igor's avatar
Markin Igor committed
    console.log("No such identity for public key");
  const serializedIdentity = await decryptMessage(encryptedIdentity, pinCode);

  const identity = new Identity(serializedIdentity);
  if (extendTtl) {
    const success = extendPinCodeTtl(key, pinCode);
    if (!success) {
      console.log("Can not extend pincode ttl");
      return null;
    }
  }
function extendPinCodeTtl(key, pinCode) {
Alexey Lunin's avatar
Alexey Lunin committed
  if (pinCode == null || pinCode === "") {
Zdravko Iliev's avatar
Zdravko Iliev committed
    const now = new Date();
    const nowMillis = now.getTime();
    const ttl = window.sessionStorage.getItem("pincodettls/" + key);
    if (ttl == null || ttl === "" || nowMillis >= parseInt(ttl)) {
Markin Igor's avatar
Markin Igor committed
      clearPinCodeTtl(key);
Alexey Lunin's avatar
Alexey Lunin committed
      return false;
Zdravko Iliev's avatar
Zdravko Iliev committed
      const ttl = now.getTime() + 4 * 60 * 60 * 1000;
      window.sessionStorage.setItem("pincodettls/" + key, ttl);
    }
  } else {
Zdravko Iliev's avatar
Zdravko Iliev committed
    const now = new Date();
    const ttl = now.getTime() + 4 * 60 * 60 * 1000;
    window.sessionStorage.setItem("pincodettls/" + key, ttl);
    window.sessionStorage.setItem("pincodes/" + key, pinCode);
  }

  return true;
}

window.extendPinCodeTtl = extendPinCodeTtl;

function clearPinCodeTtl(key) {
Markin Igor's avatar
Markin Igor committed
  window.sessionStorage.removeItem("pincodettls/" + key);
Alexey Lunin's avatar
Alexey Lunin committed
  window.sessionStorage.removeItem("pincodes/" + key);
}

function getPincode(key) {
Zdravko Iliev's avatar
Zdravko Iliev committed
  const now = new Date();
  const nowMillis = now.getTime();
  const ttl = window.sessionStorage.getItem("pincodettls/" + key);
Markin Igor's avatar
Markin Igor committed
  if (ttl == null || ttl === "") {
Alexey Lunin's avatar
Alexey Lunin committed
    return null;
  } else {
Alexey Lunin's avatar
Alexey Lunin committed
    if (nowMillis >= parseInt(ttl)) {
Markin Igor's avatar
Markin Igor committed
      clearPinCodeTtl(key);
Alexey Lunin's avatar
Alexey Lunin committed
      return null;
    } else {
      return window.sessionStorage.getItem("pincodes/" + key);
    }
  }
}

function createEvent(actionId, type, payloads) {
  return {
Alexey Lunin's avatar
Alexey Lunin committed
    actionID: actionId,
Zdravko Iliev's avatar
Zdravko Iliev committed
    type,
Alexey Lunin's avatar
Alexey Lunin committed
    stamp: new Date().getTime(),
Alexey Lunin's avatar
Alexey Lunin committed
  };
const destroyAuthentication = () => {
  const authenticationPublicKey = localStorage.getItem("authenticatedIdentity");

  window.viamApi.setIdentity("");
  window.viamApi.setSessionData("", "");

  clearPinCodeTtl(authenticationPublicKey);

  localStorage.removeItem("uuid");
  localStorage.removeItem("token");
  localStorage.removeItem("authenticatedIdentity");

  window.currentlyAuthenticatedIdentity = null;
  window.lastTimeGetProfile = 0;
};

const destroyIdentity = () => {
  destroyAuthentication();

  if (window.currentlyLoadedIdentity) {
    const { publicKey } = window.currentlyLoadedIdentity.authentication;

    delete window.loadedIdentities[publicKey];
    window.currentlyLoadedIdentity = null;
    destroyIdentityFromLocalStorage(publicKey);
  }
};

Markin Igor's avatar
Markin Igor committed
window.loadedIdentities = {};
window.wopiAPI = new WopiAPI();
window.collaboraApi = new CollaboraAPI();
window.viamApi = new ViamAPI();
window.viamAnonymousApi = new ViamAPI();
Markin Igor's avatar
Markin Igor committed
window.currentlyAuthenticatedIdentity = null;
window.currentlyLoadedIdentity = null;
window.lastTimeGetProfile = 0;
let iframeParent = null;

Markin Igor's avatar
Markin Igor committed
const handleIdentityLogin = (identity, uuid, token) => {
Sasha Ilieva's avatar
Sasha Ilieva committed
  const { viamApi } = window;
Markin Igor's avatar
Markin Igor committed
  const { publicKey } = identity.authentication;
  viamApi.setSessionData(uuid, token);
  localStorage.setItem("uuid", uuid);
  localStorage.setItem("token", token);
  localStorage.setItem("authenticatedIdentity", publicKey);
Sasha Ilieva's avatar
Sasha Ilieva committed
  window.currentlyAuthenticatedIdentity = identity;
Markin Igor's avatar
Markin Igor committed
  window.lastTimeGetProfile = 0;
  setKeyForUUID(uuid, publicKey);
async function executeRestfulFunction(type, that, fn, config, ...args) {
Alexey Lunin's avatar
Alexey Lunin committed
  const {
    currentlyAuthenticatedIdentity,
    viamApi,
Alexey Lunin's avatar
Alexey Lunin committed
  } = window;
  let response;
  try {
    response = await fn.apply(that, [config, ...args]);
  } catch (error) {
    if (error.response) {
      //Resposnse with status code != 2xx still has valid response
      response = error.response;
    } else {
      //Connection error or similar
      const data = {
Alexey Lunin's avatar
Alexey Lunin committed
        data: "",
        code: "999",
  const identity = currentlyAuthenticatedIdentity || currentlyLoadedIdentity;
  const { code, status } = response.data;
Sasha Ilieva's avatar
Sasha Ilieva committed

Alexey Lunin's avatar
Alexey Lunin committed
  const deviceRevoked =
    type === "private" && code === "401" && status === STATUS_DEVICE_REVOKED;
    const event = createEvent("", "DeviceRevoked");
    iframeParent.onEvent(event);
  const userNotActivated =
    type === "private" &&
    code === "400" &&
Sasha Ilieva's avatar
Sasha Ilieva committed
    (status === STATUS_USER_NOT_ACTIVATED || status === STATUS_USER_BLOCKED);

  if (userNotActivated) {
    destroyIdentity();

Sasha Ilieva's avatar
Sasha Ilieva committed
    const event = createEvent("", "UserBlocked");
    iframeParent.onEvent(event);

    return response.data;
  }

Alexey Lunin's avatar
Alexey Lunin committed
  const badSession =
    type === "private" &&
    identity &&
    code === "400" &&
    status === "Bad session";
Sasha Ilieva's avatar
Sasha Ilieva committed
  if (!badSession) {
    return response.data;
  }
Alexey Lunin's avatar
Alexey Lunin committed
  const loginResponse = await viamApi.identityLogin(
    null,
    "previousaddeddevice"
  );
Sasha Ilieva's avatar
Sasha Ilieva committed
  if (loginResponse.data.code !== "200") {
    return loginResponse.data;
  }

  const uuid = loginResponse.data.data["Uuid"];
  const token = loginResponse.data.data["Session"];
  handleIdentityLogin(identity, uuid, token);
  try {
    response = await fn.apply(that, [config, ...args]);
  } catch (error) {
    response = error.response;
  }
  return response.data;
}

window.executeRestfulFunction = executeRestfulFunction;

function loadIdentityInternal(identityKey, pinCode) {
  return new Penpal.Promise(result => {
Alexey Lunin's avatar
Alexey Lunin committed
    getIdentityFromLocalStorage(identityKey, pinCode)
      .then(async loadedIdentity => {
Alexey Lunin's avatar
Alexey Lunin committed
        if (loadedIdentity == null) {
          result({
            data: "",
            code: "400",
            status:
              "Please restore or authorize your account via another device."
Alexey Lunin's avatar
Alexey Lunin committed
          });
        }
        localStorage.removeItem("attempt");

        window.loadedIdentities[identityKey] = loadedIdentity;
        window.currentlyLoadedIdentity = loadedIdentity;

        if (identityKey === localStorage.getItem("authenticatedIdentity")) {
          window.currentlyAuthenticatedIdentity = loadedIdentity;
          const uuid = localStorage.getItem("uuid");
          const token = localStorage.getItem("token");
          const deviceHash = await createDeviceHash(identityKey);
          window.viamApi.setIdentity(identityKey);
          window.viamApi.setDeviceHash(deviceHash);
          window.viamApi.setSessionData(uuid, token);
        }
Alexey Lunin's avatar
Alexey Lunin committed
        window.viamAnonymousApi.setIdentity(
          window.currentlyLoadedIdentity.authentication.publicKey
        );
Sasha Ilieva's avatar
Sasha Ilieva committed

Alexey Lunin's avatar
Alexey Lunin committed
        const { publicKey, x509Certificate } = loadedIdentity.authentication;
Alexey Lunin's avatar
Alexey Lunin committed
        result({
          data: {
            authentication: {
              publicKey,
Alexey Lunin's avatar
Alexey Lunin committed
          },
          code: "200",
          status: "Identity loaded"
Alexey Lunin's avatar
Alexey Lunin committed
        });
      })
Alexey Lunin's avatar
Alexey Lunin committed
        result({
          data: "",
          code: "400",
Alexey Lunin's avatar
Alexey Lunin committed
        });
  });
}

function getCertificateForPassport(passportUUID, internal) {
  return new Penpal.Promise(certificateResult => {
    if (window.currentlyAuthenticatedIdentity === null) {
Sasha Ilieva's avatar
Sasha Ilieva committed
      return { data: "", code: "400", status: "Identity not authenticated" };
    const passportIdentity = window.currentlyAuthenticatedIdentity;
Zdravko Iliev's avatar
Zdravko Iliev committed
    const passport = passportIdentity.getPassport(passportUUID);
Alexey Lunin's avatar
Alexey Lunin committed
    if (passport === undefined || passport === null) {
Sasha Ilieva's avatar
Sasha Ilieva committed
      createPassportCertificate(passportUUID).then(function (keys) {
Zdravko Iliev's avatar
Zdravko Iliev committed
        const cryptoData = new CryptoData();
Markin Igor's avatar
Markin Igor committed
        cryptoData.setPublicKey(keys["publicKeyPEM"]);
        cryptoData.setPrivateKey(keys["privateKeyPEM"]);
Zdravko Iliev's avatar
Zdravko Iliev committed
        const certificate = keys["certificatePEM"];
        //download("passportCertificateBeforeSigning.crt", "text/plain", certificate)
        //cryptoData.setx509Certificate(keys["certificate"])
Alexey Lunin's avatar
Alexey Lunin committed
        executeRestfulFunction(
          "private",
          viamApi,
          viamApi.signSignCertificate,
          null,
          btoa(certificate),
          passportUUID
        ).then(executeResult => {
Alexey Lunin's avatar
Alexey Lunin committed
          if (executeResult.code === "200") {
Zdravko Iliev's avatar
Zdravko Iliev committed
            const signedCertificate = atob(
Alexey Lunin's avatar
Alexey Lunin committed
              executeResult.data["SignedCertificate"]
            );
            //download("passportCertificateAfterSigning.crt", "text/plain", signedCertificate)
Zdravko Iliev's avatar
Zdravko Iliev committed
            const keyUUID = executeResult.data["CertificateUUID"];
            const encodedChain = executeResult.data["Chain"];
            //download("rootCertificate.crt", "text/plain", atob(encodedChain[0]))

Zdravko Iliev's avatar
Zdravko Iliev committed
            const chain = [];
Alexey Lunin's avatar
Alexey Lunin committed
            for (let i = 0; i < encodedChain.length; i++) {
Alexey Lunin's avatar
Alexey Lunin committed
              chain.push(atob(encodedChain[i]));
Markin Igor's avatar
Markin Igor committed
            cryptoData.setx509Certificate(signedCertificate);
            cryptoData.setKeyUUID(keyUUID);
            cryptoData.setChain(chain);
Markin Igor's avatar
Markin Igor committed
            passportIdentity.setPassport(passportUUID, cryptoData);
            getProfileData(passportIdentity).then(executeResult1 => {
Alexey Lunin's avatar
Alexey Lunin committed
              setIdentityInLocalStorage(passportIdentity)
                .then(() => {
                  window.currentlyAuthenticatedIdentity = passportIdentity;
                  window.lastTimeGetProfile = 0;
                  window.currentlyLoadedIdentity = passportIdentity;
                  const copyOfCryptoData = JSON.parse(
                    JSON.stringify(cryptoData)
                  );

                  if (internal === false) {
                    copyOfCryptoData["privateKey"] = "";
                  }

                  certificateResult({
                    data: copyOfCryptoData,
                    code: "200",
                    status: "Certificate got"
Alexey Lunin's avatar
Alexey Lunin committed
                  });
                })
Alexey Lunin's avatar
Alexey Lunin committed
                  certificateResult({
                    data: "",
                    code: "400",
                    status: "Can not store certificate " + e
Alexey Lunin's avatar
Alexey Lunin committed
                  });
                });
            });
          } else {
            certificateResult(executeResult);
Zdravko Iliev's avatar
Zdravko Iliev committed
      const copyOfCryptoData = JSON.parse(JSON.stringify(passport));
Alexey Lunin's avatar
Alexey Lunin committed
      if (internal === false) {
        copyOfCryptoData["privateKey"] = "";
Alexey Lunin's avatar
Alexey Lunin committed
      certificateResult({
        data: copyOfCryptoData,
        code: "200",
        status: "Certificate got"
      });
    }
  });
}

const connection = Penpal.connectToParent({
  // Methods child is exposing to parent
  methods: {
    initialize: (apiUrl, wopiUrl, collaboraUrl) => {
      if (!apiUrl) {
        apiUrl = `${window.location.origin}/api/`;
        console.warn(`API host URL not specified. Fall back to ${apiUrl}`); // eslint-disable-line no-console
      }

      if (!wopiUrl) {
        wopiUrl = `${window.location.origin}/wopi/`;
        console.warn(`WOPI host URL not specified. Fall back to ${wopiUrl}`); // eslint-disable-line no-console
      }

      if (!collaboraUrl) {
        collaboraUrl = window.location.origin;
Alexey Lunin's avatar
Alexey Lunin committed
        console.warn(
          `Collabora host URL not specified. Fall back to ${collaboraUrl}`
        ); // eslint-disable-line no-console
Alexey Lunin's avatar
Alexey Lunin committed
      window.API_HOST =
        apiUrl.charAt(apiUrl.length - 1) === "/" ? apiUrl : apiUrl + "/";
      window.WOPI_URL =
        wopiUrl.charAt(wopiUrl.length - 1) === "/" ? wopiUrl : wopiUrl + "/";
      window.COLLABORA_URL =
Sasha Ilieva's avatar
Sasha Ilieva committed
        collaboraUrl.charAt(collaboraUrl.length - 1) === "/"
          ? collaboraUrl
          : collaboraUrl + "/";
    createIdentity(pinCode) {
      return new Penpal.Promise(result => {
Sasha Ilieva's avatar
Sasha Ilieva committed
        createPassportCertificate(makeid()).then(function (keys) {
Zdravko Iliev's avatar
Zdravko Iliev committed
          const newIdentity = new Identity();
          const cryptoData = new CryptoData();
Markin Igor's avatar
Markin Igor committed
          cryptoData.setPublicKey(keys["publicKeyPEM"]);
          cryptoData.setPrivateKey(keys["privateKeyPEM"]);
          cryptoData.setx509Certificate(keys["certificatePEM"]);
          newIdentity.setAuthentication(cryptoData);
          newIdentity.setPinCode(pinCode);
          window.currentlyLoadedIdentity = newIdentity;
          localStorage.setItem(
            "currentlyLoadedIdentity",
            JSON.stringify(newIdentity)
          );
          const { publicKey, x509Certificate } = newIdentity.authentication;
Sasha Ilieva's avatar
Sasha Ilieva committed
          window.loadedIdentities[publicKey] = newIdentity;
Markin Igor's avatar
Markin Igor committed
          extendPinCodeTtl(newIdentity.authentication.publicKey, pinCode);
Alexey Lunin's avatar
Alexey Lunin committed
          window.viamAnonymousApi.setIdentity(
            newIdentity.authentication.publicKey
          );
Alexey Lunin's avatar
Alexey Lunin committed
          result({
            data: {
              authentication: {
                publicKey,
Alexey Lunin's avatar
Alexey Lunin committed
            code: "200",
            status: "Identity created"
Alexey Lunin's avatar
Alexey Lunin committed
          });
Alexey Lunin's avatar
Alexey Lunin committed
      });
    },
    listIdentities() {
      return new Penpal.Promise(result => {
Zdravko Iliev's avatar
Zdravko Iliev committed
        const identities = listIdentitiesFromLocalStorage();
Sasha Ilieva's avatar
Sasha Ilieva committed
        result({ data: identities, code: "200", status: "Identities listed" });
      });
    },
    loadIdentity(identityKey, pinCode) {
Alexey Lunin's avatar
Alexey Lunin committed
      return loadIdentityInternal(identityKey, pinCode);
    checkIdentityPinCode: async (key, pinCode) => {
      try {
        const identity = await getIdentityFromLocalStorage(key, pinCode, false);

        if (identity) {
          return encodeResponse("200", null, "Pincode check successful");
        } else {
          return encodeResponse("400", null, "Pincode check failed");
        }
      } catch (e) {
        return encodeResponse("400", e, "Pincode check error");
      }
    },
    changeIdentityPinCode: async (key, oldPinCode, newPinCode) => {
      try {
Alexey Lunin's avatar
Alexey Lunin committed
        const identity = await getIdentityFromLocalStorage(
          key,
          oldPinCode,
          false
        );

        if (identity) {
          identity.pinCode = newPinCode;
          await setIdentityInLocalStorage(identity);
          window.currentlyAuthenticatedIdentity = identity;
          window.currentlyLoadedIdentity = identity;
igorwork's avatar
igorwork committed
          return encodeResponse("200", null, "Successfully changed pincode");
igorwork's avatar
igorwork committed
          return encodeResponse("400", null, "Identity not found");
igorwork's avatar
igorwork committed
        return encodeResponse("400", e.message, "Change pincode error");
    },
    getIdentityProfile(identityKey) {
      return new Penpal.Promise(result => {
Alexey Lunin's avatar
Alexey Lunin committed
        const serializedProfile = localStorage.getItem(
          "profiles/" + identityKey
        );
        if (serializedProfile === null || serializedProfile === "") {
Sasha Ilieva's avatar
Sasha Ilieva committed
          result({ data: "", code: "400", status: "Profile is empty" });
        } else {
Alexey Lunin's avatar
Alexey Lunin committed
          result({
            data: JSON.parse(serializedProfile),
            code: "200",
            status: "Identities cleared"
Alexey Lunin's avatar
Alexey Lunin committed
          });
    clearIdentities: async () => {
      destroyAuthentication();
Markin Igor's avatar
Markin Igor committed

      const identitiesTemp = listIdentitiesFromLocalStorage();

      for (const i in identitiesTemp) {
        destroyIdentityFromLocalStorage(i);
      }
      return encodeResponse("200", "", "Identities cleared");
    confirmIdentificator(identity, confirmationCodeArg) {
      return new Penpal.Promise(result => {
Markin Igor's avatar
Markin Igor committed
        viamApi.setIdentity(identity.authentication.publicKey);
Alexey Lunin's avatar
Alexey Lunin committed
        executeRestfulFunction(
          "public",
          viamApi,
          viamApi.identityConfirmIdentificator,
          null,
          confirmationCodeArg
        ).then(executeResult => {
          result(executeResult);
        });
      });
    },
    identityGetIdentificatorByRegisterToken(identity, tokenArg) {
      return new Penpal.Promise(result => {
Markin Igor's avatar
Markin Igor committed
        viamApi.setIdentity(identity.authentication.publicKey);
Alexey Lunin's avatar
Alexey Lunin committed
        executeRestfulFunction(
          "public",
          viamApi,
          viamApi.identityGetIdentificatorByRegisterToken,
          null,
          tokenArg
        ).then(executeResult => {
          result(executeResult);
        });
      });
    },
    submitIdentificator(identity, identificatorArg, registerToken) {
      return new Penpal.Promise(result => {
Markin Igor's avatar
Markin Igor committed
        viamApi.setIdentity(identity.authentication.publicKey);
Alexey Lunin's avatar
Alexey Lunin committed
        executeRestfulFunction(
          "public",
          viamApi,
          viamApi.identitySubmitIdentificator,
          null,
          identificatorArg,
          registerToken
        ).then(executeResult => {
Alexey Lunin's avatar
Alexey Lunin committed
    submitRegisterClaims(
      identity,
      givennameArg,
      familynameArg,
      emailArg,
      phonenumberArg
    ) {
      return new Penpal.Promise(result => {
Markin Igor's avatar
Markin Igor committed
        viamApi.setIdentity(identity.authentication.publicKey);
Alexey Lunin's avatar
Alexey Lunin committed
        executeRestfulFunction(
          "public",
          viamApi,
          viamApi.identitySubmitRegisterClaims,
          null,
          givennameArg,
          familynameArg,
          emailArg,
          phonenumberArg
        ).then(executeResult => {
    finalizeEmployeeRegistration: async (identity, identifier) => {
      viamApi.setIdentity(identity.authentication.publicKey);
      return executeRestfulFunction(
        "public",
        viamApi,
        viamApi.identityFinalizeEmployeeRegistration,
        null,
        identifier
      );
    },
    agreeOnRegistration(registerIdentity) {
      return new Penpal.Promise(result => {
Markin Igor's avatar
Markin Igor committed
        viamApi.setIdentity(registerIdentity.authentication.publicKey);
Alexey Lunin's avatar
Alexey Lunin committed
        executeRestfulFunction(
          "public",
          viamApi,
          viamApi.identityAgreeOnRegistration,
          null
        ).then(executeResult => {
Markin Igor's avatar
Markin Igor committed
          let sequence = Promise.resolve();
          if (executeResult.code === "200") {
            sequence = sequence.then(() => {
Alexey Lunin's avatar
Alexey Lunin committed
              setIdentityInLocalStorage(registerIdentity);
            });
Alexey Lunin's avatar
Alexey Lunin committed
          sequence
            .then(() => {
              result(executeResult);
Alexey Lunin's avatar
Alexey Lunin committed
              result({
                data: "",
                code: "400",
                status: "Can not store identity: " + e
Alexey Lunin's avatar
Alexey Lunin committed
              });
            });
        });
      });
    },
    resendConfirmationCode(identity, identificatorArg) {
      return new Penpal.Promise(result => {
Markin Igor's avatar
Markin Igor committed
        viamApi.setIdentity(identity.authentication.publicKey);
Alexey Lunin's avatar
Alexey Lunin committed
        executeRestfulFunction(
          "public",
          viamApi,
          viamApi.identityResendConfirmationCode,
          null,
          identificatorArg
        ).then(executeResult => {
          result(executeResult);
        });
      });
    },
Markin Igor's avatar
Markin Igor committed
    login: async (loginIdentity, mode, requestCode, requestActionID) => {
      if (!window.loadedIdentities[loginIdentity.authentication.publicKey]) {
        return {
          data: "",
          code: "400",
          status: "Identity not loaded"
Markin Igor's avatar
Markin Igor committed
        };
      }
Alexey Lunin's avatar
Alexey Lunin committed
      const deviceHash = await createDeviceHash(
        loginIdentity.authentication.publicKey
      );
      window.viamApi.setSessionData("", "");
Markin Igor's avatar
Markin Igor committed
      window.viamApi.setDeviceHash(deviceHash);
Markin Igor's avatar
Markin Igor committed
      window.viamApi.setIdentity(loginIdentity.authentication.publicKey);

Alexey Lunin's avatar
Alexey Lunin committed
      const identityLoginResponse = await executeRestfulFunction(
        "public",
        window.viamApi,
        window.viamApi.identityLogin,
        null,
        mode,
        requestCode,
        requestActionID
      );
Markin Igor's avatar
Markin Igor committed

      const { code, data } = identityLoginResponse;
      const responseToClient = Object.assign({}, identityLoginResponse);

      if (code === "200") {
Sasha Ilieva's avatar
Sasha Ilieva committed
        if (mode === LOGIN_MODES.PREVIOUSLY_ADDED_DEVICE) {
Markin Igor's avatar
Markin Igor committed
          handleIdentityLogin(loginIdentity, data.Uuid, data.Session);
Markin Igor's avatar
Markin Igor committed
          await getProfileData(loginIdentity);
        } else if (mode === LOGIN_MODES.NEW_DEVICE) {
          const dataUrl = await generateQrCode(
Alexey Lunin's avatar
Alexey Lunin committed
            `${data.ActionID},${data.QrCode}`
          );
Markin Igor's avatar
Markin Igor committed
          Object.assign(responseToClient.data, { image: dataUrl });
        }
      }

      return responseToClient;
    },
    identityAddNewDevice() {
      return new Penpal.Promise(result => {
Alexey Lunin's avatar
Alexey Lunin committed
        const authenticationPublicKey = localStorage.getItem(
          "authenticatedIdentity"
        );
        if (authenticationPublicKey === null) {
Alexey Lunin's avatar
Alexey Lunin committed
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
Alexey Lunin's avatar
Alexey Lunin committed
          });
        if (window.loadedIdentities[authenticationPublicKey] === null) {
Alexey Lunin's avatar
Alexey Lunin committed
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
Alexey Lunin's avatar
Alexey Lunin committed
          });
Zdravko Iliev's avatar
Zdravko Iliev committed
        const success = extendPinCodeTtl(authenticationPublicKey);
Alexey Lunin's avatar
Alexey Lunin committed
        if (success === false) {
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
Alexey Lunin's avatar
Alexey Lunin committed
          });
Alexey Lunin's avatar
Alexey Lunin committed
        executeRestfulFunction(
          "private",
          viamApi,
          viamApi.identityAddNewDevice,
          null
        ).then(async executeResult => {
Markin Igor's avatar
Markin Igor committed
          if (executeResult.code === "200") {
Zdravko Iliev's avatar
Zdravko Iliev committed
            const actionID = executeResult.data["ActionID"];
            const QrCode = executeResult.data["QrCode"];
            const dataUrl = await generateQrCode(actionID + "," + QrCode);
            executeResult.data["image"] = dataUrl;
            result(executeResult);
          } else {
            result(executeResult);
          }
        });
      });
    },
    identityDestroyKeysForDevice(authenticationPublicKeyArg) {
      return new Penpal.Promise(result => {
Alexey Lunin's avatar
Alexey Lunin committed
        const authenticationPublicKey = localStorage.getItem(
          "authenticatedIdentity"
        );
        if (authenticationPublicKey === null) {
Alexey Lunin's avatar
Alexey Lunin committed
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
Alexey Lunin's avatar
Alexey Lunin committed
          });
        if (window.loadedIdentities[authenticationPublicKey] === null) {
Alexey Lunin's avatar
Alexey Lunin committed
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
Alexey Lunin's avatar
Alexey Lunin committed
          });
Zdravko Iliev's avatar
Zdravko Iliev committed
        const success = extendPinCodeTtl(authenticationPublicKey);
Alexey Lunin's avatar
Alexey Lunin committed
        if (success === false) {
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
Alexey Lunin's avatar
Alexey Lunin committed
          });
Alexey Lunin's avatar
Alexey Lunin committed
        executeRestfulFunction(
          "private",
          viamApi,
          viamApi.identityDestroyKeysForDevice,
          null,
          btoa(authenticationPublicKeyArg)
        ).then(executeResult => {
          result(executeResult);
        });
      });
    },
Markin Igor's avatar
Markin Igor committed
    logout: async () => {
Alexey Lunin's avatar
Alexey Lunin committed
        const authenticationPublicKey = localStorage.getItem(
          "authenticatedIdentity"
        );
        if (
          !authenticationPublicKey ||
          !window.loadedIdentities[authenticationPublicKey]
        ) {
          return {
            data: "",
            code: "400",
            status: "Identity not loaded"
igorwork's avatar
igorwork committed
        // Clone headers to be able destroy authentication first.
        // We need it because clients should be able reload page right after logout invocation and not wait until request completed
Alexey Lunin's avatar
Alexey Lunin committed
        const headers = { ...window.viamApi.getConfig().headers };

        destroyAuthentication();
        return executeRestfulFunction(
          "private",
          window.viamApi,
          window.viamApi.identityLogout,
        );
      } catch (e) {
Markin Igor's avatar
Markin Igor committed
        return {
          data: "",
          code: "400",
Markin Igor's avatar
Markin Igor committed
        };
Sasha Ilieva's avatar
Sasha Ilieva committed
    identityRestoreAccess(restoreAccessIdentity, identificator) {
      return new Penpal.Promise(result => {
Sasha Ilieva's avatar
Sasha Ilieva committed
        viamApi.setSessionData("", "");
        viamApi.setIdentity(restoreAccessIdentity.authentication.publicKey);

        executeRestfulFunction(
          "public",
          viamApi,
          viamApi.identityRestoreAccess,
          null,
          identificator
Sasha Ilieva's avatar
Sasha Ilieva committed
        ).then(executeResult => {
          result(executeResult);
        });
    contactsCheckAccountRecoveryStatus: async () => {
      const currentlyLoadedIdentity = localStorage.getItem(
        "currentlyLoadedIdentity"
      );
      const parsedIdentity = JSON.parse(currentlyLoadedIdentity);
      window.currentlyLoadedIdentity = parsedIdentity;
      const { publicKey } = parsedIdentity.authentication;
      window.loadedIdentities[publicKey] = parsedIdentity;
      window.viamAnonymousApi.setIdentity(publicKey);
Sasha Ilieva's avatar
Sasha Ilieva committed
      window.viamApi.setSessionData("", "");
      window.viamApi.setIdentity(publicKey);
Sasha Ilieva's avatar
Sasha Ilieva committed
      const timeout = ms => new Promise(resolve => setTimeout(resolve, ms));

      async function checkAccountRecoveryStatus() {
        const response = await executeRestfulFunction(
          "public",
          viamApi,
          viamApi.contactsCheckAccountRecoveryStatus,
          null
        );
Sasha Ilieva's avatar
Sasha Ilieva committed
        if (response.data === 0) {
          await timeout(1000);
          await checkAccountRecoveryStatus();
          return;
Sasha Ilieva's avatar
Sasha Ilieva committed
        const deviceHash = await createDeviceHash(publicKey);
        window.viamApi.setDeviceHash(deviceHash);

Sasha Ilieva's avatar
Sasha Ilieva committed
        const identityLoginResponse = await executeRestfulFunction(
          "public",
          window.viamApi,
          window.viamApi.identityLogin,
          null,
          "previousaddeddevice"
        );
Sasha Ilieva's avatar
Sasha Ilieva committed

Sasha Ilieva's avatar
Sasha Ilieva committed
        const { code, data } = identityLoginResponse;
        if (code === "200") {
          await setIdentityInLocalStorage(parsedIdentity);
Sasha Ilieva's avatar
Sasha Ilieva committed
          handleIdentityLogin(parsedIdentity, data.Uuid, data.Session);
          await getProfileData(parsedIdentity);
          localStorage.removeItem("currentlyLoadedIdentity");
Sasha Ilieva's avatar
Sasha Ilieva committed
        }
Sasha Ilieva's avatar
Sasha Ilieva committed
      }
Sasha Ilieva's avatar
Sasha Ilieva committed
      await checkAccountRecoveryStatus();
    contactsGetTrusteeContactsPublicKeys: async () => {
      try {
        const response = await executeRestfulFunction(
          "private",
          window.viamApi,
          window.viamApi.contactsGetTrusteeContactsPublicKeys,
          null
        );
        if (response.code !== "200") {
          return response;
        }

Sasha Ilieva's avatar
Sasha Ilieva committed
        const responseData = response.data;
Sasha Ilieva's avatar
Sasha Ilieva committed
        const trusteesDevices = Object.values(responseData);
Sasha Ilieva's avatar
Sasha Ilieva committed

Sasha Ilieva's avatar
Sasha Ilieva committed
        /** Check if there are new trustees without added secret part */
Sasha Ilieva's avatar
Sasha Ilieva committed
        const hasNewTrustees = trusteesDevices.some(device => {
          const deviceData = Object.values(device);
          return deviceData.some(data => data.hasShamir === "0");
Sasha Ilieva's avatar
Sasha Ilieva committed
        });
Sasha Ilieva's avatar
Sasha Ilieva committed

Sasha Ilieva's avatar
Sasha Ilieva committed
        if (!hasNewTrustees) {
          return response;
Sasha Ilieva's avatar
Sasha Ilieva committed
        }
Sasha Ilieva's avatar
Sasha Ilieva committed

        // Generate and split recovery key
Sasha Ilieva's avatar
Sasha Ilieva committed
        const trusteesUuids = Object.keys(responseData);
        const trusteesToDevices = Object.entries(responseData);
        const sharesNumber = trusteesUuids.length;
        const recoveryKey = generateRecoveryKey();
        const recoveryKeyShares = getRecoveryKeyShares(
          recoveryKey,
          sharesNumber
        );
        const sanityCheckResponse = checkRecoveryKeyCombine(
Sasha Ilieva's avatar
Sasha Ilieva committed
          recoveryKeyShares
Sasha Ilieva's avatar
Sasha Ilieva committed
        if (sanityCheckResponse.code !== "200") {
          return sanityCheckResponse;
        // Encrypt each share with every publicKey of each contact device
        const shamirPartsList = await Promise.all(
Sasha Ilieva's avatar
Sasha Ilieva committed
          trusteesToDevices.map(async ([contactUuid, device], index) => {
            const deviceIdsToPublicKeys = Object.entries(device);
Sasha Ilieva's avatar
Sasha Ilieva committed
            // Encrypt secret shares in parallel
            const deviceIdsToEncryptedPartsList = await Promise.all(
              deviceIdsToPublicKeys.map(async ([deviceId, { content }]) => {
                const encryptedShare = await encryptShare(
                  recoveryKeyShares[index],
                );

                return [deviceId, encryptedShare];
              })
            );
Sasha Ilieva's avatar
Sasha Ilieva committed
            // Turn deviceIdsToEncryptedPartsList array to object
            const deviceIdsToEncryptedParts = Object.fromEntries(
              deviceIdsToEncryptedPartsList
Sasha Ilieva's avatar
Sasha Ilieva committed
            return [contactUuid, deviceIdsToEncryptedParts];
Sasha Ilieva's avatar
Sasha Ilieva committed
        // Turn shamirPartsList array to object
        const shamirParts = Object.fromEntries(shamirPartsList);
Sasha Ilieva's avatar
Sasha Ilieva committed

        // Save Shamir parts to database
        const saveShamirPartsResponse = await executeRestfulFunction(
          "private",
          window.viamApi,
          window.viamApi.contactsSaveShamirParts,
          null,
          shamirParts
        );
        if (saveShamirPartsResponse !== "200") {
          return saveShamirPartsResponse;
        }

Sasha Ilieva's avatar
Sasha Ilieva committed
        return response;
      } catch (error) {
Sasha Ilieva's avatar
Sasha Ilieva committed
        return encodeResponse("400", "", error.message);
    getCurrentlyLoggedInUUID() {
      return new Penpal.Promise(result => {
Alexey Lunin's avatar
Alexey Lunin committed
        const authenticationPublicKey = localStorage.getItem(
          "authenticatedIdentity"
        );
        if (authenticationPublicKey === null) {
Sasha Ilieva's avatar
Sasha Ilieva committed
          return { data: "", code: "400", status: "Identity not loaded" };
        if (window.loadedIdentities[authenticationPublicKey] === null) {
Sasha Ilieva's avatar
Sasha Ilieva committed
          return { data: "", code: "400", status: "Identity not loaded" };
Zdravko Iliev's avatar
Zdravko Iliev committed
        const success = extendPinCodeTtl(authenticationPublicKey);
Alexey Lunin's avatar
Alexey Lunin committed
        if (success === false) {
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
Alexey Lunin's avatar
Alexey Lunin committed
          });
Alexey Lunin's avatar
Alexey Lunin committed
        if (localStorage.getItem("uuid") === null) {
Sasha Ilieva's avatar
Sasha Ilieva committed
          result({ data: "", code: "400", status: "Not logged in UUID" });
Alexey Lunin's avatar
Alexey Lunin committed
        result({
          data: localStorage.getItem("uuid"),
          code: "200",
Alexey Lunin's avatar
Alexey Lunin committed
        });
      });
    },
    getCertificateByPassport(passportUUID) {
      return new Penpal.Promise(result => {
Alexey Lunin's avatar
Alexey Lunin committed
        const authenticationPublicKey = localStorage.getItem(
          "authenticatedIdentity"
        );
        if (authenticationPublicKey === null) {
Sasha Ilieva's avatar
Sasha Ilieva committed
          return { data: "", code: "400", status: "Identity not loaded" };
        if (window.loadedIdentities[authenticationPublicKey] === null) {
Sasha Ilieva's avatar
Sasha Ilieva committed
          return { data: "", code: "400", status: "Identity not loaded" };
Zdravko Iliev's avatar
Zdravko Iliev committed
        const success = extendPinCodeTtl(authenticationPublicKey);
Alexey Lunin's avatar
Alexey Lunin committed
        if (success === false) {
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
Alexey Lunin's avatar
Alexey Lunin committed
          });
Alexey Lunin's avatar
Alexey Lunin committed
        getCertificateForPassport(passportUUID, false).then(
Alexey Lunin's avatar
Alexey Lunin committed
            result(certificateResult);
          }
        );
      });
    },
    getOneTimeCertificateByPassport(passportUUID, emailArg) {
      return new Penpal.Promise(result => {
Alexey Lunin's avatar
Alexey Lunin committed
        const authenticationPublicKey = localStorage.getItem(
          "authenticatedIdentity"
        );
        if (authenticationPublicKey === null) {
Sasha Ilieva's avatar
Sasha Ilieva committed
          return { data: "", code: "400", status: "Identity not loaded" };
        if (window.loadedIdentities[authenticationPublicKey] === null) {
Sasha Ilieva's avatar
Sasha Ilieva committed
          return { data: "", code: "400", status: "Identity not loaded" };
Zdravko Iliev's avatar
Zdravko Iliev committed
        const success = extendPinCodeTtl(authenticationPublicKey);
Alexey Lunin's avatar
Alexey Lunin committed
        if (success === false) {
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
Alexey Lunin's avatar
Alexey Lunin committed
          });
Alexey Lunin's avatar
Alexey Lunin committed
        getCertificateForPassport(passportUUID, true).then(
Alexey Lunin's avatar
Alexey Lunin committed
            if (certificateResult.code === "200") {
Zdravko Iliev's avatar
Zdravko Iliev committed
              const passportCertificate =
Alexey Lunin's avatar
Alexey Lunin committed
                certificateResult.data["x509Certificate"];
Zdravko Iliev's avatar
Zdravko Iliev committed
              const passportPrivateKey = certificateResult.data["privateKey"];
              const passportChain = certificateResult.data["chain"];
Alexey Lunin's avatar
Alexey Lunin committed

              createOneTimePassportCertificate(
                makeid() + "-" + passportUUID,
                emailArg,
                passportPrivateKey,
                passportCertificate
Sasha Ilieva's avatar
Sasha Ilieva committed
              ).then(function (keys) {
Zdravko Iliev's avatar
Zdravko Iliev committed
                const publicKeyOneTime = keys["publicKeyPEM"];
                const privateKeyOneTime = keys["privateKeyPEM"];
                const certificateOneTime = keys["certificatePEM"];
Alexey Lunin's avatar
Alexey Lunin committed
                passportChain.push(passportCertificate);

Zdravko Iliev's avatar
Zdravko Iliev committed
                const oneTimeCryptoData = new CryptoData();
Alexey Lunin's avatar
Alexey Lunin committed
                oneTimeCryptoData.setx509Certificate(certificateOneTime);
                oneTimeCryptoData.setPrivateKey(privateKeyOneTime);
                oneTimeCryptoData.setPublicKey(publicKeyOneTime);
                oneTimeCryptoData.setChain(passportChain);

                result({
                  data: oneTimeCryptoData,
                  code: "200",
                  status: "One time certificate generated"
Alexey Lunin's avatar
Alexey Lunin committed
                });
                // Prints PEM formatted signed certificate
                // -----BEGIN CERTIFICATE-----MIID....7Hyg==-----END CERTIFICATE-----
              });
            } else {
              result({
                data: "",
                code: "400",
                status: "Can not generate one time certificate"
Alexey Lunin's avatar
Alexey Lunin committed
              });
            }
Alexey Lunin's avatar
Alexey Lunin committed
        );
    verifySMIME: async smimeString => {
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );

      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

Damyan Mitev's avatar
Damyan Mitev committed
      //TODO cache (for some time) the root certificate
      // either as PEM or as certificate object (preferred)
      const rootCaResponse = await executeRestfulFunction(
        "private",
        window.viamApi,
        window.viamApi.signRetrieveRootCertificate,
        null
      );

      if (rootCaResponse.code !== "200") {
        return encodeResponse("400", "", rootCaResponse.status);
      }

      const rootCaPem = rootCaResponse.data;
Damyan Mitev's avatar
Damyan Mitev committed
      const verificationResult = await verifySMIME(smimeString, rootCaPem);
      return encodeResponse(
        "200",
        verificationResult.verified,
        verificationResult.message
      );
    validateDocument: async (documentUUID, contentType) => {
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );

      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

      const validateDocumentResponse = await executeRestfulFunction(
        "private",
        window.viamApi,
        window.viamApi.documentValidateDocumentByUUID,
        null,
        documentUUID,

      if (validateDocumentResponse.code !== "200") {
        return encodeResponse("400", "", validateDocumentResponse.status);
      }

      const signatures = validateDocumentResponse.data;
      if (signatures) {
        for (const signature of signatures) {
          const certificateChain = signature.certificateChainPEM.map(
              const certificate = parseCertificate(certificatePEM);
              const certificateData = new CertificateData(certificate);
              return certificateData;
            }
          );
          signature.certificateChain = certificateChain;
        }
    signEmail: async (passportUUID, emailArg, emailMessage) => {
Alexey Lunin's avatar
Alexey Lunin committed
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );
      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }
      let response = await getCertificateForPassport(passportUUID, true);
      if (response.code !== "200") {
        return encodeResponse("400", "", response.status);
      }
      const {
        x509Certificate: passportCertificate,
        privateKey: passportPrivateKey,
Alexey Lunin's avatar
Alexey Lunin committed
      const keys = await createOneTimePassportCertificate(
        makeid() + "-" + passportUUID,
        emailArg,
        passportPrivateKey,
        passportCertificate
      );
Alexey Lunin's avatar
Alexey Lunin committed
      const {
        privateKeyPEM: privateKeyOneTime,
        certificatePEM: certificateOneTime
Alexey Lunin's avatar
Alexey Lunin committed
      } = keys;
      passportChain.push(passportCertificate);

      response = await executeRestfulFunction(
Alexey Lunin's avatar
Alexey Lunin committed
        "private",
        window.viamApi,
        window.viamApi.passportGetEmailWithHeaderByPassport,
        null,
        passportUUID,
        emailMessage
      );

      if (response.code !== "200") {
        return encodeResponse("400", "", response.status);
      }

Alexey Lunin's avatar
Alexey Lunin committed
      const signedEmail = await signEmail(
        response.data,
        certificateOneTime,
        passportChain,
        privateKeyOneTime
      );

      response = await executeRestfulFunction(
Alexey Lunin's avatar
Alexey Lunin committed
        "private",
        window.viamApi,
        window.viamApi.signResignEmail,
        null,
        passportUUID,
        signedEmail
      );

      if (response.code !== "200") {
        return encodeResponse("400", "", response.status);
      }

      return encodeResponse("200", response.data, "Email signed");
Damyan Mitev's avatar
Damyan Mitev committed
    signDocument: async (passportUUID, documentUUID, documentContentType) => {
Alexey Lunin's avatar
Alexey Lunin committed
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );
Damyan Mitev's avatar
Damyan Mitev committed

      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

      const certResponse = await getCertificateForPassport(passportUUID, true);
Damyan Mitev's avatar
Damyan Mitev committed

      if (certResponse.code !== "200") {
        return encodeResponse("400", "", certResponse.status);
Damyan Mitev's avatar
Damyan Mitev committed
      }

      const {
        x509Certificate: passportCertificate,
        privateKey: passportPrivateKey,
      } = certResponse.data;
Damyan Mitev's avatar
Damyan Mitev committed

Alexey Lunin's avatar
Alexey Lunin committed
      const keys = await createOneTimePassportCertificate(
        makeid() + "-" + passportUUID,
        null,
        passportPrivateKey,
        passportCertificate
      );
Damyan Mitev's avatar
Damyan Mitev committed

Alexey Lunin's avatar
Alexey Lunin committed
      const {
        privateKeyPEM: privateKeyOneTime,
        certificatePEM: certificateOneTime
Alexey Lunin's avatar
Alexey Lunin committed
      } = keys;
Damyan Mitev's avatar
Damyan Mitev committed

Gospodin Bodurov's avatar
Gospodin Bodurov committed
      passportChain.reverse();

Damyan Mitev's avatar
Damyan Mitev committed
      passportChain.push(passportCertificate);

Gospodin Bodurov's avatar
Gospodin Bodurov committed
      passportChain.reverse();

Alexey Lunin's avatar
Alexey Lunin committed
      const pdfContentType = "application/pdf";
Damyan Mitev's avatar
Damyan Mitev committed

      if (documentContentType !== pdfContentType) {
        const convResponse = await executeRestfulFunction(
Alexey Lunin's avatar
Alexey Lunin committed
          "private",
          window.viamApi,
          window.viamApi.documentConvertDocumentByUUID,
          null,
          documentUUID,
          documentContentType,
          pdfContentType
        );
Damyan Mitev's avatar
Damyan Mitev committed
        if (convResponse.code !== "200") {
          return encodeResponse("400", "", convResponse.status);
        }
      }

      const downloadResponse = await executeRestfulFunction(
Alexey Lunin's avatar
Alexey Lunin committed
        "private",
        window.viamApi,
        window.viamApi.documentGetDocumentByUUID,
        null,
        documentUUID,
        pdfContentType
      );
Damyan Mitev's avatar
Damyan Mitev committed

      if (downloadResponse.code !== "200") {
        return encodeResponse("400", "", downloadResponse.status);
      }

      const pdfRaw = base64ToByteArray(downloadResponse.data);
Damyan Mitev's avatar
Damyan Mitev committed

      let signedPdf;
      try {
Alexey Lunin's avatar
Alexey Lunin committed
        signedPdf = await signPdf(
          pdfRaw,
          certificateOneTime,
          passportChain,
          privateKeyOneTime
        );
      } catch (err) {
        console.error(err);
        return encodeResponse("500", "", err.message);
      }
Damyan Mitev's avatar
Damyan Mitev committed

      const signedPdfB64 = byteArrayToBase64(signedPdf);

      const uploadResponse = await executeRestfulFunction(
Alexey Lunin's avatar
Alexey Lunin committed
        "private",
        window.viamApi,
        window.viamApi.documentPutDocumentByUUID,
        null,
        documentUUID,
        pdfContentType,
        signedPdfB64
      );
      if (uploadResponse.code !== "200") {
        return encodeResponse("400", "", uploadResponse.status);
      }

      const signResponse = await executeRestfulFunction(
Alexey Lunin's avatar
Alexey Lunin committed
        "private",
        window.viamApi,
        window.viamApi.documentSignDocumentByUUID,
        null,
        passportUUID,
        documentUUID,
        pdfContentType
      );
      if (signResponse.code !== "200") {
        return encodeResponse("400", "", signResponse.status);
      }

      return encodeResponse("200", "", "Document signed");
Damyan Mitev's avatar
Damyan Mitev committed
    },
    signDocumentJava: async (
      passportUUID,
      documentUUID,
      documentContentType
    ) => {
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );

      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

      const certResponse = await getCertificateForPassport(passportUUID, true);

      if (certResponse.code !== "200") {
        return encodeResponse("400", "", certResponse.status);
      }

      const {
        x509Certificate: passportCertificate,
        privateKey: passportPrivateKey,
      } = certResponse.data;

      const keys = await createOneTimePassportCertificate(
        makeid() + "-" + passportUUID,
        passportPrivateKey,
        passportCertificate
      );

      const {
        privateKeyPEM: privateKeyOneTime,
        certificatePEM: certificateOneTime
Gospodin Bodurov's avatar
Gospodin Bodurov committed
      passportChain.reverse();

      passportChain.push(passportCertificate);
      passportChain.push(certificateOneTime);

Gospodin Bodurov's avatar
Gospodin Bodurov committed
      passportChain.reverse();

      const pdfContentType = "application/pdf";

      if (documentContentType !== pdfContentType) {
        const convResponse = await executeRestfulFunction(
          "private",
          window.viamApi,
          window.viamApi.documentConvertDocumentByUUID,
          null,
          documentUUID,
          documentContentType,
          pdfContentType
        );
        if (convResponse.code !== "200") {
          return encodeResponse("400", "", convResponse.status);
        }
      }

      const signResponse = await executeRestfulFunction(
        "private",
        window.viamApi,
        window.viamApi.documentSignDocumentJavaService,
        null,
        privateKeyOneTime,
        passportChain,
        passportUUID,
        documentUUID,
        pdfContentType
      );
      if (signResponse.code !== "200") {
        return encodeResponse("400", "", signResponse.status);
      }

      return encodeResponse("200", "", "Document signed");
    },
    // passportUUID - String - passport to sign the vCard
    // senderEmail - the email address of the sender
    // attribs - additional attributes, to be added to the vCard. Recognized attribs are:
    //   - emailService: "GMail", "Outlook 365", ...
    // text, html - String - the text and html part of the email, both optional
    // parts - array of objects, representing the MIME structure in format:
Damyan Mitev's avatar
Damyan Mitev committed
    // {
    //   headers: {
    //     "Content-Type": "image/jpeg",
    //     "Content-Disposition": "inline" or "attachment" with additional attributes,
    //     ... //other headers from MIME
Damyan Mitev's avatar
Damyan Mitev committed
    //   },
    //   body: String if it is a text part (Content-Type = "text/...") or Uint8Array otherwise; filled for leaf MIME nodes
    //   parts: array of instances of the same object; filled for container MIME nodes (Content-Type = "multipart/...")
Damyan Mitev's avatar
Damyan Mitev committed
    // }
    signVCard: async (
      passportUUID,
      senderEmail,
      attribs,
      textBody,
      htmlBody,
      parts
    ) => {
Damyan Mitev's avatar
Damyan Mitev committed
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );

      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

      const messageUUID = makeid();

      const vCardAttribs = {
        ...attribs,
        passportUUID,
      let vCardImageData;
      let vCardImageClaimValue;

      const vCardImageClaimName = "vCardImage";
      const defaultTagName = "notag";

      const vCardClaimResponse = await executeRestfulFunction(
Damyan Mitev's avatar
Damyan Mitev committed
        "private",
        window.viamApi,
        window.viamApi.entityGetClaim,
Damyan Mitev's avatar
Damyan Mitev committed
        null,
        vCardImageClaimName,
        defaultTagName,
Damyan Mitev's avatar
Damyan Mitev committed
        passportUUID
      );
      // if (vCardClaimResponse.code !== "200") {
      //   return encodeResponse("400", "", vCardClaimResponse.status);
      // }
      if (vCardClaimResponse.code === "200") {
        vCardImageClaimValue = vCardClaimResponse.data;
      }
      let qrCodeImageData;
      let qrCodeCoordinates = { fromL: -1, fromR: -1, toL: -1, toR: -1 };
      if (
        vCardImageClaimValue &&
        "state" in vCardImageClaimValue &&
        vCardImageClaimValue.state === "disabled"
      ) {
        vCardImageData = new ImageData({
          contentType: "image/png",
            "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=" //1x1px transparent pixel
        });
      } else {
        const vCardImageResponse = await executeRestfulFunction(
          "private",
          window.viamApi,
          window.viamApi.passportGetVCardImage,
          null,
          passportUUID
        );
Gospodin Bodurov's avatar
Gospodin Bodurov committed

        console.log(vCardImageResponse);

        if (vCardImageResponse.code !== "200") {
          return encodeResponse("400", "", vCardImageResponse.status);
        }
        vCardImageData = new ImageData(vCardImageResponse.data.Image);
        if (vCardImageData.contentType !== "image/png") {
          return encodeResponse(
            "400",
            "",
            "Content type of vCard mmust be 'image/png'"
          );
        qrCodeCoordinates = vCardImageResponse.data.QRCodeCoordinates;
        const qrCodeBase64Content = await generateQrCode(
          "https://" + location.host + "/check/" + messageUUID
        qrCodeImageData = new ImageData({
          contentType: "image/png",
          content: qrCodeBase64Content
Damyan Mitev's avatar
Damyan Mitev committed
      if (typeof parts === "undefined" || parts === null) {
Damyan Mitev's avatar
Damyan Mitev committed
        parts = [];
      }

      if (htmlBody) {
        if (typeof htmlBody !== "string") {
          throw new Error("htmlBody is not string"); // should not happen
        }
Damyan Mitev's avatar
Damyan Mitev committed
        const htmlPart = {
          headers: {
            "Content-Type": "text/html"
Damyan Mitev's avatar
Damyan Mitev committed
          },
Damyan Mitev's avatar
Damyan Mitev committed
        };
        parts.unshift(htmlPart);
      } else {
        console.log("Html body is not passed to signVCard, its value is ", {
      if (textBody) {
        if (typeof textBody !== "string") {
          throw new Error("textBody is not string"); // should not happen
        }
Damyan Mitev's avatar
Damyan Mitev committed
        const textPart = {
          headers: {
            "Content-Type": "text/plain"
Damyan Mitev's avatar
Damyan Mitev committed
          },
Damyan Mitev's avatar
Damyan Mitev committed
        };
        parts.unshift(textPart);
      } else {
        console.log("Text body is not passed to signVCard, its value is ", {
      const count = await prepareVCardParts(parts);
      if (count.textParts === 0) {
        return encodeResponse("400", "", "No text parts passed to signVCard");
      }
      if (count.htmlParts === 0) {
        return encodeResponse("400", "", "No html parts passed to signVCard");
      }
Damyan Mitev's avatar
Damyan Mitev committed
      const certResponse = await getCertificateForPassport(passportUUID, true);

      if (certResponse.code !== "200") {
        return encodeResponse("400", "", certResponse.status);
      }

      const {
        x509Certificate: passportCertificate,
        privateKey: passportPrivateKey,
Damyan Mitev's avatar
Damyan Mitev committed
      } = certResponse.data;

      const keys = await createOneTimePassportCertificate(
        makeid() + "-" + passportUUID,
        senderEmail,
Damyan Mitev's avatar
Damyan Mitev committed
        passportPrivateKey,
        passportCertificate
      );

      const {
        privateKeyPEM: privateKeyOneTime,
        certificatePEM: certificateOneTime
Damyan Mitev's avatar
Damyan Mitev committed
      } = keys;

      passportChain.reverse();

      passportChain.push(passportCertificate);
      passportChain.push(certificateOneTime);

      passportChain.reverse();

      console.log(qrCodeImageData);
      console.log(qrCodeCoordinates);
Gospodin Bodurov's avatar
Gospodin Bodurov committed

Damyan Mitev's avatar
Damyan Mitev committed
      const signVCardResponse = await executeRestfulFunction(
        "private",
        window.viamApi,
        window.viamApi.signSignVCardForChain,
        null,
        vCardImageData,
        privateKeyOneTime,
        passportChain,
        vCardAttribs,
        qrCodeImageData,
Damyan Mitev's avatar
Damyan Mitev committed
      );
      if (signVCardResponse.code !== "200") {
        return encodeResponse("400", "", signVCardResponse.status);
      }

Damyan Mitev's avatar
Damyan Mitev committed
      const signedVCardImageData = new ImageData(signVCardResponse.data);
      return encodeResponse(
        "200",
        {
          image: signedVCardImageData,
Damyan Mitev's avatar
Damyan Mitev committed
    },
    // mime - String - the MIME of the email message
    // vCardAttribs - optional attributes for the verification procedure in format
Damyan Mitev's avatar
Damyan Mitev committed
    // {
    //   passportUUID: passportUUID,
    //   messageUUID: messageUUID
    // };
Damyan Mitev's avatar
Damyan Mitev committed
    validateVMime: async (vMime, vCardAttribs = null) => {
Damyan Mitev's avatar
Damyan Mitev committed
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );

      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

      const validateVMimeResponse = await executeRestfulFunction(
Damyan Mitev's avatar
Damyan Mitev committed
        "private",
        window.viamApi,
        window.viamApi.signValidateVMime,
Damyan Mitev's avatar
Damyan Mitev committed
        null,
Damyan Mitev's avatar
Damyan Mitev committed
        vMime,
Damyan Mitev's avatar
Damyan Mitev committed
      );
      if (validateVMimeResponse.code !== "200") {
        return encodeResponse("400", "", validateVMimeResponse.status);
Damyan Mitev's avatar
Damyan Mitev committed
      const validationResult = validateVMimeResponse.data;
      const { signatures } = validationResult;
      if (signatures) {
        for (const signature of signatures) {
          const certificateChain = signature.certificateChainPEM.map(
              const certificate = parseCertificate(certificatePEM);
              const certificateData = new CertificateData(certificate);
              return certificateData;
            }
          );
          signature.certificateChain = certificateChain;
      return encodeResponse(
        "200",
        validationResult,
        "Validation result retrieved"
      );
Damyan Mitev's avatar
Damyan Mitev committed
    },
    generateQrCode,
    documentCreateDocument: async (passportUUID, path, contentType, title) => {
Alexey Lunin's avatar
Alexey Lunin committed
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );
      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

      path = encodeURI(path);
      contentType = encodeURI(contentType);
      title = encodeURI(title);

      const config = {
        headers: {
          path,
          passportuuid: passportUUID,
Alexey Lunin's avatar
Alexey Lunin committed
      const response = await executeRestfulFunction(
        "private",
        window.viamApi,
        window.viamApi.documentCreateDocument,
        config
      );
      if (response.code !== "200") {
        return encodeResponse("400", "", response.status);
      }

      return encodeResponse("200", response.data, "Document created");
    },
Alexey Lunin's avatar
Alexey Lunin committed
    documentPutDocument: async (
      passportUUID,
      resourceid,
      contentType,
Alexey Lunin's avatar
Alexey Lunin committed
      file,
      upload
Alexey Lunin's avatar
Alexey Lunin committed
    ) => {
      const authenticationPublicKey = localStorage.getItem(
        "authenticatedIdentity"
      );
      if (
        !authenticationPublicKey ||
        !window.loadedIdentities[authenticationPublicKey] ||
        !extendPinCodeTtl(authenticationPublicKey)
      ) {
        return encodeResponse("400", "", "Identity not authenticated");
      }

      resourceid = encodeURI(resourceid);
      contentType = encodeURI(contentType);

Alexey Lunin's avatar
Alexey Lunin committed
          "Content-Type": "multipart/form-data",
          contentType,

      const response = await executeRestfulFunction(
Alexey Lunin's avatar
Alexey Lunin committed
        "private",
        window.viamApi,
        window.viamApi.documentPutDocument,
        config,
        file
      );
      if (response.code !== "200") {
        return encodeResponse("400", "", response.status);
      }

      return encodeResponse("200", response.data, "Document stored");
    },
    hasSession() {
      return new Penpal.Promise(result => {
Alexey Lunin's avatar
Alexey Lunin committed
        const authenticationPublicKey = localStorage.getItem(
          "authenticatedIdentity"
        );
        if (authenticationPublicKey === null) {
Alexey Lunin's avatar
Alexey Lunin committed
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
        if (window.loadedIdentities[authenticationPublicKey] === null) {
Alexey Lunin's avatar
Alexey Lunin committed
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
Zdravko Iliev's avatar
Zdravko Iliev committed
        const success = extendPinCodeTtl(authenticationPublicKey);
Alexey Lunin's avatar
Alexey Lunin committed
        if (success === false) {
          result({
            data: "",
            code: "400",
            status: "Identity not authenticated"
Alexey Lunin's avatar
Alexey Lunin committed
          });
Alexey Lunin's avatar
Alexey Lunin committed
        executeRestfulFunction(
          "private",
          viamApi,
          viamApi.identityHasSession,
          null
        ).then(executeResult => {
          result(executeResult);
        });
      });
    },
    marketingSignUpIdentificator(identificator, reference) {
      return new Penpal.Promise(result => {
Markin Igor's avatar
Markin Igor committed
        viamApi.setIdentity("marketingapppublickey");
Alexey Lunin's avatar
Alexey Lunin committed
        executeRestfulFunction(
          "public",
          viamApi,
          viamApi.marketingSignUpIdentificator,
          null,
          identificator,
          reference
        ).then(executeResult => {
Markin Igor's avatar
Markin Igor committed
          viamApi.setIdentity("");
Markin Igor's avatar
Markin Igor committed
          viamApi.setSessionData("", "");
          result(executeResult);
        });
      });
    },
    marketingGetIdentificatorProfile(identificator, pincode) {
      return new Penpal.Promise(result => {
Markin Igor's avatar
Markin Igor committed
        viamApi.setIdentity("marketingapppublickey");
Alexey Lunin's avatar
Alexey Lunin committed
        executeRestfulFunction(
          "public",
          viamApi,
          viamApi.marketingGetIdentificatorProfile,
          null,
          identificator,
          pincode
        ).then(executeResult => {
Markin Igor's avatar
Markin Igor committed
          viamApi.setIdentity("");
Markin Igor's avatar
Markin Igor committed
          viamApi.setSessionData("", "");
          result(executeResult);
        });
      });
    },
    marketingExecuteEventForIdentificator(identificator, pincode, event) {
      return new Penpal.Promise(result => {
Markin Igor's avatar
Markin Igor committed
        viamApi.setIdentity("marketingapppublickey");
Alexey Lunin's avatar
Alexey Lunin committed
        executeRestfulFunction(
          "public",
          viamApi,
          viamApi.marketingExecuteEventForIdentificator,
          null,
          identificator,
          pincode,
          event
        ).then(executeResult => {
Markin Igor's avatar
Markin Igor committed
          viamApi.setIdentity("");
Markin Igor's avatar
Markin Igor committed
          viamApi.setSessionData("", "");
          result(executeResult);
        });
      });
    },
    getCurrentlyAuthenticatedIdentity() {
Alexey Lunin's avatar
Alexey Lunin committed
      const {
        publicKey,
Alexey Lunin's avatar
Alexey Lunin committed
      } = window.currentlyAuthenticatedIdentity.authentication;

      return encodeResponse(
        "200",
        {
          authentication: {
            publicKey,
    extractMessageID(mime) {
      return new Penpal.Promise(result => {
        result(extractMessageID(mime));
      });
    },
    stringToUtf8ByteArray(str) {
      return new Penpal.Promise(result => {
        result(stringToUtf8ByteArray(str));
      });
      return new Penpal.Promise(result => {
        result(utf8ByteArrayToString(ba));
      });
Loading
Loading full blame...