Sitemap

Strapi V4 Upload Plugin customize: Enabling SHA1 Checksum Hashing

4 min readJun 7, 2023

Introduction

Strapi, a highly flexible open-source Headless CMS, provides numerous ways to customize and enhance its default behavior. One of these is the ability to override the service methods of its plugins. In this tutorial, we’ll focus on the Upload Plugin of Strapi V4 and show how to add a checksum hashing functionality using SHA1. This enhancement will allow API consumers to dismiss files if they didn’t change by comparing hash keys.

Adding SHA1 Checksum Hashing

A checksum hash, like SHA1, uniquely represents data. Any tiny change in the data results in a completely different hash. This characteristic makes it ideal for checking file integrity and detecting changes in content.

To provide this feature in our Strapi instance, we’ll override a method in the Upload Plugin and add a functionality to calculate the SHA1 checksum for each uploaded file.

Here’s the TypeScript code to accomplish this, for this tutorial we will override two methods from the upload function

1 -enhanceAndValidateFile

2 -formatFileInfo

In order to modify or extend the code of a core plugin, the initial step involves registering our methods within the index.ts file and importing the customized version.

//index.ts

import { enhanceAndValidateFileOver } from './extensions/upload/enhanceAndValidateFileOverride';
import { formatFileInfoOverride } from './extensions/upload/formatFileInfoOverride';





register({ strapi }) {
strapi.services["plugin::upload.upload"].enhanceAndValidateFile = enhanceAndValidateFileOver;
strapi.services["plugin::upload.upload"].formatFileInfo = formatFileInfoOverride;
},

Then we will create two files under /extensions/upload/

//extensions/upload/enhanceAndValidateFileOverride.ts

import { ReadStream } from 'fs';
import * as fs from 'fs';
import * as crypto from 'crypto';

interface FileInfo {
filename: string;
type: string;
size: number;
sha1sum?: string;
getStream?: () => ReadStream;
[key: string]: any;
}

interface Metas {
tmpWorkingDirectory?: string;
[key: string]: any;
}

export async function enhanceAndValidateFileOver(file: any, fileInfo: FileInfo, metas: Metas = {}): Promise<FileInfo> {


const hash = crypto.createHash('sha1');
const stream = fs.createReadStream(file.path);

stream.on('data', (data: Buffer) => {
hash.update(data);
});

const sha1sum = await new Promise<string>((resolve, reject) => {
stream.on('end', () => {
resolve(hash.digest('hex'));
});
stream.on('error', reject);
});

fileInfo.hash = sha1sum


const currentFile = await this.formatFileInfo(
{
filename: file.name,
type: file.type,
size: file.size,
sha1sum,
},
fileInfo,
{
...metas,
tmpWorkingDirectory: file.tmpWorkingDirectory,
}
);

currentFile.getStream = () => fs.createReadStream(file.path);

const { optimize, isImage, isFaultyImage, isOptimizableImage } = strapi
.plugin('upload')
.service('image-manipulation');

if (await isImage(currentFile)) {
if (await isFaultyImage(currentFile)) {
throw new Error('File is not a valid image');
}
if (await isOptimizableImage(currentFile)) {
return optimize(currentFile);
}
}
return currentFile;
}



In enhanceAndValidateFileOverride, we computes the SHA1 hash of a file before it’s uploaded and parse it to formatFileInfo method

//extensions/upload/formatFileInfoOverride'
import _ from "lodash";
import path from "path";
import { extension } from "mime-types";
import fs from 'fs';
import crypto from 'crypto';

interface FileInfo {
name?: string;
folder?: string;
caption?: string;
alternativeText?: string;
hash?:string
}

interface Metas {
refId?: string;
ref?: string;
field?: string;
path?: string;
tmpWorkingDirectory?: string;
}

interface Entity {
ext: string;
mime: string;
hash: string;
name: string;
folder?: string;
caption?: string;
alternativeText?: string;
size: number;
folderPath: string;
related?: Array<{ id: string; __type: string; __pivot: { field: string } }>;
path?: string;
tmpWorkingDirectory?: string;
}

/**
* Overriding formatFileInfo function for upload plugin to
* maintain original file naming.
*/
export function formatFileInfoOverride(
{ filename, type, size }: { filename: string; type: string; size: number },
fileInfo: FileInfo = {},
metas: Metas = {}
): Entity {
const fileService = strapi.plugin("upload").service("file");


let ext = path.extname(filename);

if (!ext) {
ext = `.${extension(type)}`;
}
const usedName = (fileInfo.name || filename);
const basename = path.basename(usedName, ext);
const entity: Entity = {
ext,
mime: type,
hash: fileInfo.hash,
name: usedName,
folder: fileInfo.folder,
caption: fileInfo.caption,
alternativeText: fileInfo.alternativeText,
size: Math.round((size / 1000) * 100) / 100,
folderPath: fileService.getFolderPath(fileInfo.folder),
};

const { refId, ref, field } = metas;

if (refId && ref && field) {
entity.related = [{
id: refId,
__type: ref,
__pivot: { field },
}];
}

if (metas.path) {
entity.path = metas.path;
}

if (metas.tmpWorkingDirectory) {
entity.tmpWorkingDirectory = metas.tmpWorkingDirectory;
}

return entity;
}

The formatFileInfoOverride adds it to the hash key.

Benefits of Checksum Hashing

Checksum hashing provides several advantages for APIs and their consumers:

  1. File Integrity: The SHA1 checksum allows API consumers to verify that the file hasn’t been altered since it was uploaded. If the file changes, the hash will also change.
  2. Caching Efficiency: API consumers can compare the hash of a file they have cached to the hash provided by the API. If the hashes match, there’s no need to download the file again. This saves bandwidth and makes the application more responsive.
  3. Duplication Avoidance: By comparing the hash of a new file to hashes of previously uploaded files, we can avoid storing duplicates. This can save substantial storage space in the long run.

Conclusion

Strapi’s flexibility is one of its major strengths. As we’ve shown, by overriding the Upload Plugin’s methods, we can easily add features like checksum hashing. This not only gives us a way to verify file integrity but also increases caching efficiency and helps to avoid duplicate files. With such enhancements, we can make our Strapi API more robust and more efficient for consumers.

Remember, checksum hashes like SHA1 are cryptographic representations of data and any minor change in data will lead to a completely different hash. Therefore, they are a very effective way to detect changes in files or any other data.

--

--

No responses yet