zulip / zulip

Zulip server and web application. Open-source team chat that helps teams stay productive and focused.
https://zulip.com
Apache License 2.0
21.36k stars 7.76k forks source link

Add a tus-js-client urlStorage implementation based on JS maps #31926

Open timabbott opened 1 week ago

timabbott commented 1 week ago

As documented in https://github.com/tus/tus-js-client/blob/main/docs/api.md#urlstorage, it should be possible to instead of disabling this bit of tusd functionality, just provide a little module that uses JS maps to implement the API that tus-js-client expects here, without using browser local storage and the security concerns that come with it.

zulipbot commented 1 week ago

Hello @zulip/server-misc members, this issue was labeled with the "area: uploads" label, so you may want to check it out!

devyk100 commented 1 week ago

@zulipbot claim

zulipbot commented 1 week ago

Hello @devyk100!

Thanks for your interest in Zulip! You have attempted to claim an issue without the label "help wanted". You can only claim and submit pull requests for issues with the help wanted label.

If this is your first time here, we recommend reading our guide for new contributors before getting started.

hustlernik commented 23 hours ago

@timabbott Can I take up this issue?

hustlernik commented 23 hours ago

@timabbott I have gone through the PS, looked into tus-js-client npm package and tus protocol.

This is how I am thinking to implement this:

The MapUrlStorage class uses JavaScript's Map to store and retrieve upload URLs for tus-js-client, following the required UrlStorage interface. Each upload is associated with a file's fingerprint, and a unique urlStorageKey is generated for each upload. This in-memory storage avoids using browser local storage, making it suitable for environments where local storage may raise security concerns.

Sample code Snippet:

class MapUrlStorage {
  constructor() {
    this.storage = new Map();
  }

  async findAllUploads() {
    return Array.from(this.storage.values()).flat();
  }

  async findUploadsByFingerprint(fingerprint) {
    return this.storage.get(fingerprint) || [];
  }

  async removeUpload(urlStorageKey) {
    for (const [fingerprint, uploads] of this.storage.entries()) {
      const updated = uploads.filter(entry => entry.urlStorageKey !== urlStorageKey);
      updated.length ? this.storage.set(fingerprint, updated) : this.storage.delete(fingerprint);
    }
  }

  async addUpload(fingerprint, upload) {
    const urlStorageKey = `upload-${Date.now()}-${Math.random().toString(36).slice(2)}`;
    const newUpload = { ...upload, urlStorageKey };
    this.storage.set(fingerprint, [...(this.storage.get(fingerprint) || []), newUpload]);
    return urlStorageKey;
  }
}