Source: management-system/src/backend/shared-electron-server/data/iam/shares.js

import uuid from 'uuid';
import bcrypt from 'bcryptjs';
import store from '../store.js';
import { TYPE_USER, TYPE_LINK } from '../../../../shared-frontend-backend/constants/index.js';
import { config } from '../../../server/iam/utils/config.js';

export let sharesMetaObjects = {};

const resourceStoreMapping = {
  Process: 'processes',
  Project: 'processes',
  Template: 'processes',
};

// function to create random string, which is appended to url, for public shares
function getRandomString(length) {
  var randomChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  var result = '';
  for (var i = 0; i < length; i++) {
    result += randomChars.charAt(Math.floor(Math.random() * randomChars.length));
  }
  return result;
}

function addOrUpdateShare(share) {
  if (!sharesMetaObjects[share.resourceType]) {
    sharesMetaObjects = {
      ...sharesMetaObjects,
      [share.resourceType]: { [share.resourceId]: { [share.sharedWith]: share } },
    };
  }
  if (!sharesMetaObjects[share.resourceType][share.resourceId]) {
    sharesMetaObjects[share.resourceType] = {
      ...sharesMetaObjects[share.resourceType],
      [share.resourceId]: { [share.sharedWith]: share },
    };
  }
  if (!sharesMetaObjects[share.resourceType][share.resourceId][share.sharedWith]) {
    sharesMetaObjects[share.resourceType][share.resourceId] = {
      ...sharesMetaObjects[share.resourceType][share.resourceId],
      [share.sharedWith]: share,
    };
  }
}

function shareExists(share) {
  if (
    !sharesMetaObjects[share.resourceType] ||
    !sharesMetaObjects[share.resourceType][share.resourceId]
  ) {
    return false;
  } else {
    Object.values(sharesMetaObjects[share.resourceType][share.resourceId]).some((sh) => {
      switch (sh.type) {
        case TYPE_USER:
          return (
            (sh.sharedWith === share.sharedWith && sh.resourceId === share.resourceId) || // checks if share in general exists
            (sh.resourceId === share.resourceId && // checks if share exists with the same values
              sh.permissions == share.permissions &&
              sh.type == share.type) ||
            sh.id === share.id
          );
        case TYPE_LINK:
          return sh.type == share.type && sh.resourceId === share.resourceId;
        default:
          return false;
      }
    });
  }
}

/**
 * Returns shares in form of an array
 *
 * @returns {Array} - array containing shares
 */
export async function getShares() {
  return Object.values(Object.values(Object.values(sharesMetaObjects)[0]))
    .map((obj) => Object.values(obj))
    .flat();
}

/**
 * Returns a share based on share id
 *
 * @param {String} shareId - the id of a share
 * @returns {Array} - array containing all shares
 */
export async function getShare(shareId) {
  const shares = await getShares();
  return shares.find((share) => share.id === shareId);
}

/**
 * Creates a new share
 *
 * @param {Object} share - the new share object
 * @param {Number} share.permissions - allowed permissions (mandatory)
 * @param {String} share.resourceType - type of resource (mandatory)
 * @param {String} share.resourceId - id of resource (mandatory)
 * @param {0|1|2} share.type - type of sharing (mandatory) - 0 = sharing from user to user, 1 = sharing from user to group, 2 = link sharing
 * @param {String} share.sharedWith - id of a user (mandatory if not link sharing)
 * @param {String} share.password - password for a link sharing (only for type link, optional)
 * @param {Date} share.expiredAt - date when a sharing should expire (optional)
 * @param {String} share.note - a note for users that receive the sharing (optional)
 * @returns {Object} - new share object
 */
export async function addShare(share) {
  if (shareExists(share)) {
    throw new Error('share already exists!');
  }

  share.resourceType = share.resourceType[0].toUpperCase() + share.resourceType.slice(1); //ensure capital types
  const resources = store.get(resourceStoreMapping[share.resourceType]);
  const resource = resources.find((resource) => resource.id === share.resourceId);

  if (resource) {
    share.resourceOwner = resource.owner;
  } else {
    throw new Error('Resource not found!');
  }

  if (share.type === TYPE_LINK) {
    const saltRounds = 10;
    share.password = share.password ? await bcrypt.hash(share.password, saltRounds) : null;
    share.token = getRandomString(15);
    share.sharedWith = share.token;
    share.url = config.MS_URL;
  }

  // set creation date
  const createdOn = new Date().toUTCString();
  share.createdOn = createdOn;
  share.updatedOn = createdOn;

  // set share id
  const id = uuid.v4();
  share.id = id;
  if (shareExists(share)) {
    throw new Error('Share with same id already exists!');
  }

  if (!share.permissions) share.permissions = 1; // default permissions
  if (!share.expiredAt) share.expiredAt = null; // default

  // store share in local cache
  addOrUpdateShare(share);

  // store share in db
  store.add('shares', share);

  return share;
}

/**
 * Updates a share based on share id
 *
 * @param {String} shareId - the id of a share object
 * @param {Object} updates - the updated share object properties
 * @param {Number} updates.permissions - allowed permissions (optional)
 * @param {String} updates.password - password for a link sharing (only for type link, optional)
 * @param {Date} updates.expiredAt - date when a sharing should expire (optional)
 * @param {String} updates.note - a note for users that receive the sharing (optional)
 * @returns {Object} - updated share object
 */
export async function updateShare(shareId, updates) {
  const share = await getShare(shareId);
  if (!share) {
    throw new Error('Share not found!');
  }

  if (share.resourceId !== updates.resourceId || share.resourceType !== updates.resourceType) {
    throw new Error("Id of resource or type of resource doesn't match!");
  }

  if (updates.password && share.type === TYPE_LINK) {
    const saltRounds = 10;
    share.password = await bcrypt.hash(updates.password, saltRounds);
  }

  if (updates.note) share.note = updates.note;
  if (updates.permissions) share.permissions = updates.permissions;
  if (updates.expiredAt) share.expiredAt = updates.expiredAt;

  // set new updated on time
  const updatedOn = new Date().toUTCString();
  share.updatedOn = updatedOn;

  // update share in local cache
  addOrUpdateShare(share);

  // store share in db
  store.update('shares', shareId, share);

  return share;
}

/**
 * Deletes a share based on share id
 *
 * @param {Object} shareId - id share object
 * @returns {String} - id share object
 */
export async function deleteShare(shareId) {
  const share = await getShare(shareId);
  if (!share) {
    throw new Error('Share not found!');
  }

  const shareTemp = share;
  // delete share in local cache
  delete sharesMetaObjects[share.resourceType][share.resourceId][share.sharedWith];

  // delete share in db
  store.remove('shares', shareId);

  return shareTemp;
}

/**
 * initializes the shares meta information objects
 */
export async function init() {
  sharesMetaObjects = {};

  // get resources that were persistently stored
  const storedShares = store.get('shares');

  // set resources store
  store.set('shares', 'shares', storedShares);

  // set resources store cache for quick access
  storedShares.forEach((share) => {
    addOrUpdateShare(share);
  });
}

init();