cleaned up all chunk routes

This commit is contained in:
subnub
2024-12-22 16:39:59 -05:00
parent 7576e89bba
commit 37be61f657
4 changed files with 182 additions and 217 deletions
+21 -106
View File
@@ -56,47 +56,21 @@ class FileController {
}
};
getFullThumbnail = async (req: RequestTypeFullUser, res: Response) => {
getFullThumbnail = async (
req: RequestTypeFullUser,
res: Response,
next: NextFunction
) => {
if (!req.user) {
return;
}
let responseSent = false;
try {
const user = req.user;
const fileID = req.params.id;
const { decipher, readStream, file } =
await this.chunkService.getFullThumbnail(user, fileID);
readStream.on("error", (e: Error) => {
console.log("Get full thumbnail read stream error", e);
if (!responseSent) {
responseSent = true;
res.status(500).send("Server error getting full thumbnail");
}
});
decipher.on("error", (e: Error) => {
console.log("Get full thumbnail decipher error", e);
if (!responseSent) {
responseSent = true;
res.status(500).send("Server error gettingfull thumbnail");
}
});
res.set("Content-Type", "binary/octet-stream");
res.set(
"Content-Disposition",
'attachment; filename="' + file.filename + '"'
);
res.set("Content-Length", file.metadata.size.toString());
readStream.pipe(decipher).pipe(res);
await this.chunkService.getFullThumbnail(user, fileID, res);
} catch (e: unknown) {
if (e instanceof Error) {
console.log("\nGet Thumbnail Full Error File Route:", e.message);
}
res.status(500).send("Server error getting image");
next(e);
}
};
@@ -121,35 +95,18 @@ class FileController {
}
};
getPublicDownload = async (req: RequestType, res: Response) => {
let responseSent = false;
getPublicDownload = async (
req: RequestType,
res: Response,
next: NextFunction
) => {
try {
const ID = req.params.id;
const tempToken = req.params.tempToken;
const { readStream, decipher, file } =
await this.chunkService.getPublicDownload(ID, tempToken, res);
readStream.on("error", (e: Error) => {
console.log("read stream error", e);
});
decipher.on("error", (e: Error) => {
console.log("decipher stream error", e);
});
res.set("Content-Type", "binary/octet-stream");
res.set(
"Content-Disposition",
'attachment; filename="' + file.filename + '"'
);
res.set("Content-Length", file.metadata.size.toString());
readStream.pipe(decipher).pipe(res);
await this.chunkService.getPublicDownload(ID, tempToken, res);
} catch (e: unknown) {
if (e instanceof Error) {
console.log("\nGet Public Download Error File Route:", e.message);
}
next(e);
}
};
@@ -397,65 +354,23 @@ class FileController {
}
};
streamVideo = async (req: RequestTypeFullUser, res: Response) => {
streamVideo = async (
req: RequestTypeFullUser,
res: Response,
next: NextFunction
) => {
if (!req.user) {
return;
}
let responseSent = false;
try {
const user = req.user;
const fileID = req.params.id;
const headers = req.headers;
const { decipher, readStream, head } =
await this.chunkService.streamVideo(user, fileID, headers);
const cleanUp = () => {
if (readStream) readStream.destroy();
if (decipher) decipher.end();
};
const handleError = (e: Error) => {
console.log("stream video read stream error", e);
cleanUp();
};
readStream.on("error", handleError);
decipher.on("error", handleError);
readStream.on("end", () => {
if (!responseSent) {
responseSent = true;
res.end();
}
cleanUp();
});
readStream.on("close", () => {
cleanUp();
});
decipher.on("end", () => {
if (!responseSent) {
responseSent = true;
res.end();
}
cleanUp();
});
decipher.on("close", () => {
cleanUp();
});
res.writeHead(206, head);
readStream.pipe(decipher).pipe(res);
await this.chunkService.streamVideo(user, fileID, headers, res);
} catch (e: unknown) {
if (e instanceof Error) {
console.log("\nStream Video Error File Route:", e.message);
}
next(e);
}
};
+19 -101
View File
@@ -26,6 +26,7 @@ import getFolderBusboyData from "./utils/getFolderUploadBusboyData";
import { getStorageActions } from "./actions/helper-actions";
import getThumbnailData from "./utils/getThumbnailData";
import getFileData from "./utils/getFileData";
import getPublicFileData from "./utils/getPublicFileData";
const fileDB = new FileDB();
const folderDB = new FolderDB();
@@ -352,38 +353,20 @@ class StorageService {
return bufferData;
};
getFullThumbnail = async (user: UserInterface, fileID: string) => {
const userID = user._id;
const file = await fileDB.getFileInfo(fileID, userID.toString());
if (!file) throw new NotFoundError("File Thumbnail Not Found");
const password = user.getEncryptionKey();
const IV = file.metadata.IV;
if (!password) throw new ForbiddenError("Invalid Encryption Key");
const readStreamParams = createGenericParams({
filePath: file.metadata.filePath,
Key: file.metadata.s3ID,
});
const readStream = storageActions.createReadStream(readStreamParams);
const CIPHER_KEY = crypto.createHash("sha256").update(password).digest();
const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, IV);
return {
readStream,
decipher,
file,
};
getFullThumbnail = async (
user: UserInterface,
fileID: string,
res: Response
) => {
await getFileData(res, fileID, user);
};
streamVideo = async (user: UserInterface, fileID: string, headers: any) => {
streamVideo = async (
user: UserInterface,
fileID: string,
headers: any,
res: Response
) => {
const userID = user._id;
const currentFile = await fileDB.getFileInfo(fileID, userID.toString());
@@ -397,20 +380,17 @@ class StorageService {
const range = headers.range;
const parts = range.replace(/bytes=/, "").split("-");
let start = parseInt(parts[0], 10);
let end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
const IV = currentFile.metadata.IV;
const start = parseInt(parts[0], 10);
const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
const chunksize = end - start + 1;
let head = {
const head = {
"Content-Range": "bytes " + start + "-" + end + "/" + fileSize,
"Accept-Ranges": "bytes",
"Content-Length": chunksize,
"Content-Type": "video/mp4",
};
let currentIV = IV;
let fixedStart = 0;
let fixedEnd =
currentFile.length % 16 === 0
@@ -428,75 +408,13 @@ class StorageService {
fixedStart = 0;
}
const readStreamParams = createGenericParams({
filePath: currentFile.metadata.filePath,
Key: currentFile.metadata.s3ID,
});
res.writeHead(206, head);
if (fixedStart !== 0 && start !== 0) {
currentIV = (await storageActions.getPrevIV(
readStreamParams,
fixedStart - 16
)) as Buffer;
}
const readStream = storageActions.createReadStreamWithRange(
readStreamParams,
fixedStart,
fixedEnd
);
const CIPHER_KEY = crypto.createHash("sha256").update(password).digest();
const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, currentIV);
decipher.setAutoPadding(false);
return {
readStream,
decipher,
file: currentFile,
head,
};
await getFileData(res, fileID, user, { start: fixedStart, end: fixedEnd });
};
getPublicDownload = async (fileID: string, tempToken: any, res: Response) => {
const file = await fileDB.getPublicFile(fileID);
if (!file || !file.metadata.link || file.metadata.link !== tempToken) {
throw new NotAuthorizedError("File Not Public");
}
if (file.metadata.linkType === "one") {
await fileDB.removeOneTimePublicLink(fileID);
}
const user = await userDB.getUserInfo(file.metadata.owner);
if (!user) throw new NotFoundError("User Not Found");
const password = user.getEncryptionKey();
if (!password) throw new ForbiddenError("Invalid Encryption Key");
const IV = file.metadata.IV;
const readStreamParams = createGenericParams({
filePath: file.metadata.filePath,
Key: file.metadata.s3ID,
});
const readStream = storageActions.createReadStream(readStreamParams);
const CIPHER_KEY = crypto.createHash("sha256").update(password).digest();
const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, IV);
return {
readStream,
decipher,
file: file,
};
await getPublicFileData(res, fileID, tempToken);
};
deleteMulti = async (
@@ -12,7 +12,12 @@ const fileDB = new FileDB();
const storageActions = getStorageActions();
const proccessData = (res: Response, fileID: string, user: UserInterface) => {
const proccessData = (
res: Response,
fileID: string,
user: UserInterface,
range?: { start: number; end: number }
) => {
const eventEmitter = new EventEmitter();
const processFile = async () => {
@@ -32,12 +37,27 @@ const proccessData = (res: Response, fileID: string, user: UserInterface) => {
Key: currentFile.metadata.s3ID,
});
const readStream = storageActions.createReadStream(readStreamParams);
let readStream;
if (range) {
readStream = storageActions.createReadStreamWithRange(
readStreamParams,
range.start,
range.end
);
} else {
readStream = storageActions.createReadStream(readStreamParams);
}
const CIPHER_KEY = crypto.createHash("sha256").update(password).digest();
const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, IV);
if (range) {
// TODO: Check if we need this for video streaming
decipher.setAutoPadding(false);
}
decipher.on("error", (e: Error) => {
eventEmitter.emit("error", e);
});
@@ -50,12 +70,14 @@ const proccessData = (res: Response, fileID: string, user: UserInterface) => {
eventEmitter.emit("error", e);
});
res.set("Content-Type", "binary/octet-stream");
res.set(
"Content-Disposition",
'attachment; filename="' + currentFile.filename + '"'
);
res.set("Content-Length", currentFile.metadata.size.toString());
if (!range) {
res.set("Content-Type", "binary/octet-stream");
res.set(
"Content-Disposition",
'attachment; filename="' + currentFile.filename + '"'
);
res.set("Content-Length", currentFile.metadata.size.toString());
}
readStream
.pipe(decipher)
@@ -73,9 +95,14 @@ const proccessData = (res: Response, fileID: string, user: UserInterface) => {
return eventEmitter;
};
const getFileData = (res: Response, fileID: string, user: UserInterface) => {
const getFileData = (
res: Response,
fileID: string,
user: UserInterface,
range?: { start: number; end: number }
) => {
return new Promise((resolve, reject) => {
const eventEmitter = proccessData(res, fileID, user);
const eventEmitter = proccessData(res, fileID, user, range);
eventEmitter.on("finish", (data) => {
resolve(data);
});
@@ -0,0 +1,105 @@
import { EventEmitter } from "stream";
import { UserInterface } from "../../../models/user-model";
import { Response } from "express";
import ForbiddenError from "../../../utils/ForbiddenError";
import NotFoundError from "../../../utils/NotFoundError";
import crypto from "crypto";
import { createGenericParams } from "./storageHelper";
import { getStorageActions } from "../actions/helper-actions";
import FileDB from "../../../db/mongoDB/fileDB";
import NotAuthorizedError from "../../../utils/NotAuthorizedError";
import UserDB from "../../../db/mongoDB/userDB";
const fileDB = new FileDB();
const userDB = new UserDB();
const storageActions = getStorageActions();
const proccessData = (res: Response, fileID: string, tempToken: string) => {
const eventEmitter = new EventEmitter();
const processFile = async () => {
try {
const file = await fileDB.getPublicFile(fileID);
if (!file || !file.metadata.link || file.metadata.link !== tempToken) {
throw new NotAuthorizedError("File Not Public");
}
if (file.metadata.linkType === "one") {
await fileDB.removeOneTimePublicLink(fileID);
}
const user = await userDB.getUserInfo(file.metadata.owner);
if (!user) throw new NotFoundError("User Not Found");
const password = user.getEncryptionKey();
if (!password) throw new ForbiddenError("Invalid Encryption Key");
const IV = file.metadata.IV;
const readStreamParams = createGenericParams({
filePath: file.metadata.filePath,
Key: file.metadata.s3ID,
});
const readStream = storageActions.createReadStream(readStreamParams);
const CIPHER_KEY = crypto.createHash("sha256").update(password).digest();
const decipher = crypto.createDecipheriv("aes256", CIPHER_KEY, IV);
decipher.on("error", (e: Error) => {
eventEmitter.emit("error", e);
});
readStream.on("error", (e: Error) => {
eventEmitter.emit("error", e);
});
res.on("error", (e: Error) => {
eventEmitter.emit("error", e);
});
res.set("Content-Type", "binary/octet-stream");
res.set(
"Content-Disposition",
'attachment; filename="' + file.filename + '"'
);
res.set("Content-Length", file.metadata.size.toString());
readStream
.pipe(decipher)
.pipe(res)
.on("finish", () => {
eventEmitter.emit("finish");
});
} catch (e) {
eventEmitter.emit("error", e);
}
};
processFile();
return eventEmitter;
};
const getPublicFileData = (
res: Response,
fileID: string,
tempToken: string
) => {
return new Promise((resolve, reject) => {
const eventEmitter = proccessData(res, fileID, tempToken);
eventEmitter.on("finish", (data) => {
resolve(data);
});
eventEmitter.on("error", (e) => {
reject(e);
});
});
};
export default getPublicFileData;