DocuBox: Securely store, access and share your files on cloud

DocuBox: Securely store, access and share your files on cloud

No need to worry about data breach! This is the only open source solution you need for all your files

ยท

7 min read

About our Team ๐Ÿค

I am Ishant. I am a full stack web developer, an android app developer and a UI designer. Currently I am pursuing my Bachelors in Computer Science. I have been exploring different tech stacks since past 6 years and over the journey, I have worked on multiple open source, freelancing and production based projects.

Vaibhav is an android app developer and a UI designer. He has contributed to multiple open source projects. He is an expert in writing clean and scalable code. Currently, he is also pursuing his Bachelors in Computer Science.

Together, we have formed a team named Team 404 for this hackathon!

Idea ๐Ÿ’ก

Recently, me and my friend vaibhav came with an idea of a cloud file storing app which is open source and can be used by others to manage their confidential data. This application will have all the necessary features to provide users a fast access to their files on the cloud.

Additionally, we also wanted the app to be flexible and scalable so that other developers and companies can customise the way they want and deploy the app's backend to their own server

Story behind the idea, how and why ๐Ÿ˜ƒ

Almost everyone has some kind of private data in their smartphone โ€” pictures, videos, and contacts. However, as the time goes, their device's storage starts getting filled. At this stage, a person may think of using a cloud file storage platform to store their files however, such platforms are not 100% free and also come with a risk of data breach.

Data breaches and leaks are now a part of life, so it's no wonder that people want to protect their data. The demand for encryption and anonymization of data is increasing every year. In today's world, it is very important to protect your data from others (hackers, other companies, governments).

While there are many different solutions such as Google Drive or Mega Storage which are known to be secure however, nothing is better than a similar app which is open source and highly customizable!

graphicB.png

DocuBox

DocuBox is an open-source app where you can securely upload your files on the cloud โ€“ from family pictures and audio recordings to spreadsheets, presentations and other confidential documents. All the files uploaded by the users are stored in an encrypted format.

Try out the app by clicking on the button below: DocuBox

Video Demonstration

App Features โœ…

  • User Authentication - Supports email based authentication
  • File Uploading - You can upload your files on DocuBox and access it from anywhere around the world.
  • File Encryption - Files stored on DocuBox are stored in encrypted format, so your privacy remains protected.
  • File Sharing - You can share the access of your files with other users.
  • File Management - You can create multiple folders to organize your files in the way you want.
  • File Download - You can download your files and save them locally for offline usage.
  • File Search - Enhanced file searching which can give results quickly.

Insights into the app ๐Ÿ““

graphicC.png

DocuBox enables users to share the access of their documents with their friends and colleagues

graphicD.png

All the files are securely encrypted in the database so there is no major risk of data breaching

User Interface ๐Ÿ–

ui.png

The user interface of this app was designed using figma. For banners, we used canva and for mockups, we used Adobe XD.

Technical details ๐Ÿ’ป

DocuBox comprises of an Android App and a Nodejs Backend which is currently deployed on Linode. Both of them follows the Best Coding Practices including SOLID principles, Modern Appliction Development (M.A.D) practices and Design Architectures.

Android App

summary.png

  • DocuBox's android part is written using Kotlin.
  • It follows all the best practices and software development principles like SOLID which make it a very good learning resource for beginners as well as for people looking to improve their software design skills.
  • DocuBox makes use of Retrofit library to make API calls to the Nodejs server.
  • It uses Dagger Hilt to implement dependency injection.
  • It uses Kotlin Coroutines and Flow to handle all asynchronous tasks.
  • It follows MVVM architecture. It also has a G.O.A.T rating in Android's M.A.D scorecard.
  • DocuBox's code is properly linted using Ktlint.

Backend

postman.jpg

  • DocuBox's backend part is written using Nodejs and Expressjs.
  • It makes use of MongoDB to store the meta data of files and Linode's object storage to store the actual files of users.
  • It makes use of JWT to securely authenticate users
  • It makes use of AWS SDK along with Multer to upload files to Linode's object storage.
  • DocuBox's backend makes use of MVC architecture.
  • It makes use of EJS templating engine to enable users to view their documents.
  • It makes use of Mongoose Encryption library to encrypt the files stored in database

Backend Deployment ๐Ÿ”ง

linode.png

The backend of DocuBox is deployed on Linode which is a popular cloud hosting service provider platform. Linode offers various server options including debian, ubuntu and centOS. Moreover, linode also provides an object storage to upload all the files, an inbuilt support for sql databases, support for marketplace and lots of other stuff. Linode also has a comprehensive documentation for each OS and also provides a beginner friendly interface for new users.

Challenges we faced and how we solved them ๐Ÿ‘จโ€๐Ÿš€

  • The biggest challenge we faced in the backend part was to implement the delete folder functionality. This is because a folder can contain multiple files and folders and this branching of tree can go really deep. Luckily, after visiting countless stackoverflow forums, we were finally able to write the correct logic to implement this feature in backend.
  • In the android part, the biggest challenge we faced was to implement the folder navigation feature. In the documents section of app, a user can click on a folder to see the contents inside it and similarly, he can navigate to the deepest depths of a folder. The more number of subfolders a folder contains, the more API requests will be sent to the server. This can lead to higher bandwidth consumption. Therefore to resolve this issue, we made use of StateFlows and a custom stack variable to cache the data.

Code Snippets

This is how we transfer files from nodejs backend to linode's object storage

// Configuring storage variables
dotenv.config();
const storageRegion = process.env.STORAGE_REGION;
const storageUrl = `https://${storageRegion}.linodeobjects.com`;

// Setup AWS Client
aws.config.update({
  secretAccessKey: process.env.STORAGE_SECRET_KEY,
  accessKeyId: process.env.STORAGE_ACCESS_KEY,
  endpoint: new aws.Endpoint(storageUrl),
});

// Aws S3 Client Object
s3 = new aws.S3();

// Create a Multer Storage Object
const multerStorage = multer({
  storage: multerS3({
    s3: s3,
    acl: "public-read",
    bucket: process.env.STORAGE_BUCKET,
    key: function (req, file, cb) {
      console.log(file);
      cb(null, getUniqueFileName(file.originalname));
    },
  })
});

This is how we delete a file from linode's object storage

const deleteFileFromStorage = async (storageFileName) => {
  aws.config.update({
    accessKeyId: process.env.STORAGE_ACCESS_KEY,
    secretAccessKey: process.env.STORAGE_SECRET_KEY,
    region: process.env.STORAGE_REGION,
  });
  const s3 = new aws.S3();

  const params = {
    Bucket: process.env.STORAGE_BUCKET,
    Key: storageFileName,
  };

  try {
    await s3.headObject(params).promise();
    try {
      await s3.deleteObject(params).promise();
      return true;
    } catch (err) {
      return false;
    }
  } catch (err) {
    return false;
  }
};

This is how we fetch files and folders in app. To ensure good performance, we fetch both parallely.

private fun getAllData(directory: String?, overrideProgressBar: Boolean = false) = viewModelScope.launch {
    _uiState.update { it.copy(storageItems = emptyList()) }
    listOf(
        async { getFiles(directory, overrideProgressBar) },
        async { getFolders(directory, overrideProgressBar) }
    ).awaitAll()
}

private suspend fun getFiles(directory: String?, overrideProgressBar: Boolean = false) {
    storageRepo.getAllFiles(directory).collectLatest {
        updateLoadingState(it, overrideProgressBar)
        when (it) {
            is Resource.Error -> _events.emit(DocumentsScreenEvents.ShowToast(it.message))
            is Resource.Loading -> Unit
            is Resource.Success -> it.data?.let(this::handleGetFileSuccess)
        }
    }
}

private suspend fun getFolders(directory: String?, overrideProgressBar: Boolean = false) {
    storageRepo.getAllFolders(directory).collectLatest {
        updateLoadingState(it, overrideProgressBar)
        when (it) {
            is Resource.Error -> _events.emit(DocumentsScreenEvents.ShowToast(it.message))
            is Resource.Loading -> Unit
            is Resource.Success -> it.data?.let(this::handleGetFolderSuccess)
        }
    }
}

This is how we use Kotlin Flows to asynchronously make the REST API Calls in IO thread and collect stream of Data.

suspend fun getAllFiles(fileDirectory: String?) = flow {
    emit(Resource.Loading())
    emit(storageDataSource.getAllFiles(fileDirectory, token))
}.map { resource -> 
    resource mapTo { fileMapper.toLocal(it.fileList) }
}.flowOn(Dispatchers.IO)

suspend fun getAllFolders(folderParentDirectory: String?) = flow {
    emit(Resource.Loading())
    emit(storageDataSource.getAllFolders(folderParentDirectory, token))
}.map { resource -> 
    resource mapTo { folderMapper.toLocal(it.folderList) }
}.flowOn(Dispatchers.IO)

This is where the actual REST API call is made, in the bottom most DataSource layer.

To avoid repetitive try catch block and error handling code, those part is abstracted out in a function safeApiCall to make the code much more readable.

suspend fun getAllFiles(fileDirectory: String?, token: String) = safeApiCall {
    service.getFiles(GetFileRequest(fileDirectory), token.asJwt())
}

suspend fun getAllFolders(folderParentDirectory: String?, token: String) = safeApiCall {
    service.getFolders(GetFolderRequest(folderParentDirectory), token.asJwt())
}

Source Code

If you want to have a look at the complete source code of DocuBox, here are the links to the Github Repositories:

Team Members

Vote of thanks ๐Ÿ‘

We would like to thank Linode for providing such an excellent documentation and Q/A community. Special thanks to the members of Hashnode discord server for providing such a good support.

ย