Finally seems like video streaming works correctly with encryption, now just need to improve folder deletion and cleanup the backend

This commit is contained in:
subnub
2020-04-30 16:43:19 -04:00
parent 6706b751e2
commit 1fb0b350ca
19 changed files with 297 additions and 30 deletions
+2 -6
View File
@@ -14,6 +14,7 @@ interface RequestType extends Request {
user?: UserInterface,
auth?: any,
busboy: any,
encryptedTempToken?: any,
}
class FileSystemController {
@@ -314,6 +315,7 @@ class FileSystemController {
try {
console.log("Incoming Video Stream Request");
const user = req.user;
const cookie = req.headers.uuid as string;
@@ -442,8 +444,6 @@ class FileSystemController {
const user = req.user;
const fileID = req.params.id;
const headers = req.headers;
console.log("stream request", req.params.id)
await fileSystemService.streamVideo(user, fileID, headers, res);
@@ -465,13 +465,9 @@ class FileSystemController {
}
try {
console.log("download request")
const user = req.user;
const fileID = req.params.id;
//await fileService.downloadFile(user, fileID, res);
await fileSystemService.downloadFile(user, fileID, res);
} catch (e) {
+1 -1
View File
@@ -22,7 +22,7 @@ const auth = async(req: RequestType, res: Response, next: NextFunction) => {
const token = req.header("Authorization")!.replace("Bearer ", "");
const decoded = await jwt.verify(token, env.password!) as jwtType;
const decoded = jwt.verify(token, env.password!) as jwtType;
const iv = decoded.iv;
+1 -1
View File
@@ -23,7 +23,7 @@ const tempAuth = async(req: RequestType, res: Response, next: NextFunction) => {
const token = req.params.tempToken;
const decoded = await jwt.verify(token, env.password!) as jwtType;
const decoded = jwt.verify(token, env.password!) as jwtType;
const iv = decoded.iv;
+1 -1
View File
@@ -24,7 +24,7 @@ const tempAuthVideo = async(req: RequestType, res: Response, next: NextFunction)
const token = req.params.tempToken;
const decoded = await jwt.verify(token, env.password!) as jwtType;
const decoded = jwt.verify(token, env.password!) as jwtType;
const iv = decoded.iv;
+4 -2
View File
@@ -267,8 +267,8 @@ userSchema.methods.generateTempAuthToken = async function() {
const iv = crypto.randomBytes(16);
const user = this;
const token = jwt.sign({_id:user._id.toString(), iv}, env.password, {expiresIn:2});
const user = this as UserInterface;
const token = jwt.sign({_id:user._id.toString(), iv}, env.password, {expiresIn: "3000ms"});
const publicKey = user.publicKey;
const encryptionKey = user.getEncryptionKey();
@@ -276,6 +276,8 @@ userSchema.methods.generateTempAuthToken = async function() {
user.tempTokens = user.tempTokens.concat({token: encryptedToken});
//console.log("generate temp auth token", user);
await user.save();
return token;
}
@@ -20,6 +20,10 @@ import streamToBuffer from "../../utils/streamToBuffer";
import User from "../../models/user";
import env from "../../enviroment/env";
import { removeChunksFS } from "./utils/awaitUploadStreamFS";
import removeTempToken from "./utils/removeTempToken";
import getPrevIVFS from "./utils/getPrevIVFS";
import awaitStreamVideo from "./utils/awaitStreamVideo";
import fixStartChunkLength from "./utils/fixStartChunkLength";
const dbUtilsFile = new DbUtilFile();
@@ -209,29 +213,51 @@ class FileSystemService implements ChunkInterface {
let end = parts[1]
? parseInt(parts[1], 10)
: fileSize-1
const chunksize = (end-start)+1
const IV = currentFile.metadata.IV.buffer
const chunksize = (end-start)+1
let head = {
'Content-Range': 'bytes ' + start + '-' + end + '/' + fileSize,
'Accept-Ranges': 'bytes',
'Content-Length': chunksize,
'Content-Type': 'video/mp4'}
const readStream = fs.createReadStream(currentFile.metadata.filePath!,
{start: start,
end: end});
let currentIV = IV;
let fixedStart = start % 16 === 0 ? start : fixStartChunkLength(start);
if (+start === 0) {
fixedStart = 0;
}
const fixedEnd = currentFile.length; //end % 16 === 0 ? end + 15: (fixEndChunkLength(end) - 1) + 16;
const differenceStart = start - fixedStart;
if (fixedStart !== 0 && start !== 0) {
currentIV = await getPrevIVFS(fixedStart - 16, currentFile.metadata.filePath!);
}
const readStream = fs.createReadStream(currentFile.metadata.filePath!, {
start: fixedStart,
end: fixedEnd,
});
const CIPHER_KEY = crypto.createHash('sha256').update(password).digest()
const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, IV);
const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, currentIV);
decipher.setAutoPadding(false);
res.writeHead(206, head);
const allStreamsToErrorCatch = [readStream, decipher];
await awaitStream(readStream.pipe(decipher), res, allStreamsToErrorCatch);
readStream.pipe(decipher);
await awaitStreamVideo(start, end, differenceStart, decipher, res, allStreamsToErrorCatch);
}
getPublicDownload = async(fileID: string, tempToken: any, res: Response) => {
+28 -4
View File
@@ -22,6 +22,9 @@ import removeChunks from "../FileService/utils/removeChunks";
import getBusboyData from "./utils/getBusboyData";
import ChunkInterface from "./utils/ChunkInterface";
import fixStartChunkLength from "./utils/fixStartChunkLength";
import getPrevIVMongo from "./utils/getPrevIVMongo";
import awaitStreamVideo from "./utils/awaitStreamVideo";
const conn = mongoose.connection;
@@ -243,24 +246,45 @@ class MongoService implements ChunkInterface {
'Content-Length': chunksize,
'Content-Type': 'video/mp4'}
let currentIV = IV;
let fixedStart = start % 16 === 0 ? start : fixStartChunkLength(start);
if (+start === 0) {
fixedStart = 0;
}
const fixedEnd = currentFile.length; //end % 16 === 0 ? end + 15: (fixEndChunkLength(end) - 1) + 16;
const differenceStart = start - fixedStart;
if (fixedStart !== 0 && start !== 0) {
currentIV = await getPrevIVMongo(fixedStart - 16, fileID);
}
const bucket = new mongoose.mongo.GridFSBucket(conn.db, {
chunkSizeBytes: 1024
});
const readStream = bucket.openDownloadStream(new ObjectID(fileID), {
start: start,
end: end
start: fixedStart,
end: fixedEnd,
});
const CIPHER_KEY = crypto.createHash('sha256').update(password).digest()
const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, IV);
const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, currentIV);
decipher.setAutoPadding(false);
res.writeHead(206, head);
const allStreamsToErrorCatch = [readStream, decipher];
await awaitStream(readStream.pipe(decipher), res, allStreamsToErrorCatch);
readStream.pipe(decipher);
await awaitStreamVideo(start, end, differenceStart, decipher, res, allStreamsToErrorCatch);
}
deleteFile = async(userID: string, fileID: string) => {
+27 -4
View File
@@ -20,6 +20,10 @@ import imageChecker from "../../utils/imageChecker";
import Thumbnail, {ThumbnailInterface} from "../../models/thumbnail";
import streamToBuffer from "../../utils/streamToBuffer";
import removeChunksS3 from "../FileService/utils/removeChunksS3";
import fixStartChunkLength from "./utils/fixStartChunkLength";
import fixEndChunkLength from "./utils/fixEndChunkLength";
import getPrevIVS3 from "./utils/getPrevIVS3";
import awaitStreamVideo from "./utils/awaitStreamVideo";
import DbUtilFile from "../../db/utils/fileUtils/index";
const dbUtilsFile = new DbUtilFile();
@@ -159,20 +163,39 @@ class S3Service implements ChunkInterface {
'Content-Length': chunksize,
'Content-Type': 'video/mp4'}
const params: any = {Bucket: env.s3Bucket, Key: currentFile.metadata.s3ID!, Range: `bytes=${start}-${end}`};
let currentIV = IV;
let fixedStart = start % 16 === 0 ? start : fixStartChunkLength(start);
if (+start === 0) {
fixedStart = 0;
}
const fixedEnd = fileSize % 16 === 0 ? fileSize : fixEndChunkLength(fileSize); //end % 16 === 0 ? end + 15: (fixEndChunkLength(end) - 1) + 16;
const differenceStart = start - fixedStart;
if (fixedStart !== 0 && start !== 0) {
currentIV = await getPrevIVS3(fixedStart - 16, currentFile.metadata.s3ID!);
}
const params: any = {Bucket: env.s3Bucket, Key: currentFile.metadata.s3ID!, Range: `bytes=${fixedStart}-${fixedEnd}`};
const s3ReadStream = s3.getObject(params).createReadStream();
const CIPHER_KEY = crypto.createHash('sha256').update(password).digest()
const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, IV);
const decipher = crypto.createDecipheriv('aes256', CIPHER_KEY, currentIV);
res.writeHead(206, head);
const allStreamsToErrorCatch = [s3ReadStream, decipher];
await awaitStream(s3ReadStream.pipe(decipher), res, allStreamsToErrorCatch);
s3ReadStream.pipe(decipher);
await awaitStreamVideo(start, end, differenceStart, decipher, res, allStreamsToErrorCatch);
}
getThumbnail = async(user: UserInterface, id: string) => {
@@ -0,0 +1,70 @@
import {Response} from "express"
const awaitStreamVideo = (start: number, end:number, differenceStart: number,
decipher: any, res: Response, streamsToErrorCatch: any[]) => {
return new Promise((resolve, reject) => {
let firstBytesRemoved = false;
let sizeCounter = 0;
decipher.on("data", (data: Buffer | string) => {
if (+start === 0 && +end === 1) {
const dataCoverted = data.toString("hex");
let neededData = dataCoverted.substring(0, 4);
const dataBack = Buffer.from(neededData, "hex");
res.write(dataBack);
res.flush();
return;
}
if (!firstBytesRemoved) {
const dataCoverted = data.toString("hex");
let neededData = dataCoverted.substring(differenceStart * 2);
const dataBack = Buffer.from(neededData, "hex");
firstBytesRemoved = true;
sizeCounter += dataBack.length;
res.write(dataBack);
res.flush();
return;
}
res.write(data);
res.flush();
sizeCounter += data.length;
})
decipher.on("end", () => {
res.end();
resolve();
})
streamsToErrorCatch.forEach((currentStream) => {
currentStream.on("error", (e: Error) => {
reject({
message: "Await Video Stream Input Error",
code: 500,
error: e
})
})
})
})
}
export default awaitStreamVideo;
@@ -0,0 +1,6 @@
const fixEndChunkLength = (length: number) => {
return Math.floor((length-1) / 16) * 16 + 16;
}
export default fixEndChunkLength;
@@ -0,0 +1,6 @@
const fixStartChunkLength = (length: number) => {
return Math.floor((length-1) / 16) * 16 - 16;
}
export default fixStartChunkLength;
@@ -0,0 +1,19 @@
import fs from "fs";
const getPrevIV = (start: number, path: string) => {
return new Promise<Buffer | string>((resolve, reject) => {
const stream = fs.createReadStream(path, {
start,
end: start + 15
})
stream.on("data", (data) => {
resolve(data);
})
})
}
export default getPrevIV;
@@ -0,0 +1,23 @@
import mongoose from "../../../db/mongoose";
import { ObjectID } from "mongodb"
const conn = mongoose.connection;
const getPrevIV = (start: number, fileID: string) => {
return new Promise<Buffer | string>((resolve, reject) => {
const bucket = new mongoose.mongo.GridFSBucket(conn.db);
const stream = bucket.openDownloadStream(new ObjectID(fileID), {
start: start,
end: start + 16
});
stream.on("data", (data) => {
resolve(data);
})
})
}
export default getPrevIV;
@@ -0,0 +1,19 @@
import s3 from "../../../db/s3";
import env from "../../../enviroment/env";
const getPrevIV = (start: number, key: string) => {
return new Promise<Buffer | string>((resolve, reject) => {
const params: any = {Bucket: env.s3Bucket, Key: key, Range: `bytes=${start}-${start + 15}`};
const stream = s3.getObject(params).createReadStream();
stream.on("data", (data) => {
resolve(data);
})
})
}
export default getPrevIV;
@@ -0,0 +1,13 @@
import { UserInterface } from "../../../models/user";
const removeTempToken = async(user: UserInterface, tempToken: any,) => {
user.tempTokens = user.tempTokens.filter((filterToken) => {
return filterToken.token !== tempToken;
})
await user.save();
}
export default removeTempToken;
+1 -1
View File
@@ -8,7 +8,7 @@ import PhotoViewer from "../PhotoViewer"
const HomePage = (props) => (
<div>
<div className="outter-wrapper">
<Header goHome={props.goHome}/>
<div className="main-page" >
+10
View File
@@ -74,8 +74,18 @@ class MainSectionContainer extends React.Component {
document.body.appendChild(link);
link.href = finalUrl;
link.setAttribute('type', 'hidden');
link.setAttribute("download", true);
link.click();
// const form = document.createElement("form");
// form.action = finalUrl;
// form.method = "GET";
// form.setAttribute("type", "hidden");
// document.body.append(form);
// form.submit();
}).catch((err) => {
console.log(err)
})
+13 -1
View File
@@ -6,9 +6,21 @@
border-bottom: 1px solid #dadce0;
justify-content: space-between;
/* display: none; */
position: absolute;
position: fixed;
/* top: 0; */
width: 100%;
@media (max-width: $desktop-breakpoint) {
text-align: center;
display: flex;
flex-direction: row;
border-bottom: 1px solid #dadce0;
justify-content: space-between;
position: initial;
width: 100%;
height: 80px;
padding: 0;
}
}
.header__title {
+19 -1
View File
@@ -23,7 +23,11 @@
overflow-x: hidden;
@media (max-width: $desktop-breakpoint) {
width: 100vw;
// margin-top: 77px;
display: flex;
flex-direction: column;
overflow-x: hidden;
width: unset;
}
}
@@ -36,12 +40,26 @@
overflow: hidden;
}
.outter-wrapper {
height: 100vh;
width: 100vw;
}
.main-page {
background: white;
height: 100vh;
width: 100vw;
display: inline-flex;
padding-top: 71px;
@media (max-width: $desktop-breakpoint) {
background: #fff;
/* height: 100vh; */
height: calc(100% - 80px);
width: 100vw;
display: inline-flex;
padding: 0;
}
}