diff --git a/backend/controllers/file.ts b/backend/controllers/file.ts index 82e0859..b4da59e 100644 --- a/backend/controllers/file.ts +++ b/backend/controllers/file.ts @@ -6,6 +6,7 @@ import S3Service from "../services/ChunkService/S3Service"; import {UserInterface} from "../models/user"; import tempStorage from "../tempStorage/tempStorage"; import sendShareEmail from "../utils/sendShareEmail"; +import { createStreamVideoCookie } from "../cookies/createCookies"; const fileService = new FileService() @@ -330,30 +331,55 @@ class FileController { } } - getDownloadTokenVideo = async(req: RequestTypeFullUser, res: Response) => { + getAccessTokenStreamVideo = async(req: RequestTypeFullUser, res: Response) => { + + if (!req.user) return; - if (!req.user) { - return - } - try { - + const user = req.user; - const cookie = req.headers.uuid as string; - - const tempToken = await fileService.getDownloadTokenVideo(user, cookie); - - res.send({tempToken}); - + + const ipAddress = req.clientIp; + + const streamVideoAccessToken = await user.generateAuthTokenStreamVideo(ipAddress); + + createStreamVideoCookie(res, streamVideoAccessToken); + + res.send(); + } catch (e) { - const code = e.code || 500; - - console.log(e); - res.status(code).send() + console.log("\nGet Access Token Stream Video Fle Route Error:", e.message); + const code = !e.code ? 500 : e.code >= 400 && e.code <= 599 ? e.code : 500; + res.status(code).send(); } + } + // getDownloadTokenVideo = async(req: RequestTypeFullUser, res: Response) => { + + // if (!req.user) { + // return + // } + + // try { + + // const user = req.user; + // const cookie = req.headers.uuid as string; + + // const tempToken = await fileService.getDownloadTokenVideo(user, cookie); + + // res.send({tempToken}); + + // } catch (e) { + + // const code = e.code || 500; + + // console.log(e); + // res.status(code).send() + // } + // } + removeTempToken = async(req: RequestTypeFullUser, res: Response) => { if (!req.user) { diff --git a/backend/cookies/createCookies.ts b/backend/cookies/createCookies.ts index ee6a7d8..5473795 100644 --- a/backend/cookies/createCookies.ts +++ b/backend/cookies/createCookies.ts @@ -2,6 +2,7 @@ import {Response } from "express"; const maxAgeAccess = 1000 * 5;//60 * 1000 * 20; const maxAgeRefresh = 60 * 1000 * 60 * 24 * 30; +const maxAgeStreamVideo = 60 * 1000 * 60 * 24; export const createLoginCookie = (res: Response, accessToken: string, refreshToken: string) => { @@ -35,4 +36,14 @@ export const createLogoutCookie = (res: Response) => { sameSite: "strict", secure: process.env.NODE_ENV === "production" }) +} + +export const createStreamVideoCookie = (res: Response, streamVideoAccessToken: string) => { + + res.cookie("video-access-token", streamVideoAccessToken, { + httpOnly: true, + maxAge: maxAgeStreamVideo, + sameSite: "strict", + secure: process.env.NODE_ENV === "production" + }) } \ No newline at end of file diff --git a/backend/express-routers/file.ts b/backend/express-routers/file.ts index d770d58..e366af0 100644 --- a/backend/express-routers/file.ts +++ b/backend/express-routers/file.ts @@ -7,6 +7,7 @@ import FileSystemService from "../services/ChunkService/FileSystemService"; import S3Service from "../services/ChunkService/S3Service"; import ChunkInterface from "../services/ChunkService/utils/ChunkInterface"; import authFullUser from "../middleware/authFullUser"; +import authStreamVideo from "../middleware/authStreamVideo"; let fileController: FileController; let chunkService: ChunkInterface; @@ -48,11 +49,17 @@ router.get("/file-service/quick-list", auth, fileController.getQuickList); router.get("/file-service/list", auth, fileController.getList); -router.get("/file-service/download/get-token", authFullUser, fileController.getDownloadToken); +//router.get("/file-service/download/get-token", authFullUser, fileController.getDownloadToken); -router.get("/file-service/download/get-token-video", auth, fileController.getDownloadTokenVideo); +//router.get("/file-service/download/get-token-video", auth, fileController.getDownloadTokenVideo); -router.get("/file-service/stream-video/:id/:tempToken/:uuid", auth, fileController.streamVideo); +router.get("/file-service/download/access-token-stream-video", authFullUser, fileController.getAccessTokenStreamVideo) + +router.get("/file-service/stream-video/:id", authStreamVideo, fileController.streamVideo); + +//router.get("/file-service/stream-video/:id/:tempToken/:uuid", auth, fileController.streamVideo); + +//router.get("/file-service/stream-video/:id", auth, fileController.streamVideo); router.get("/file-service/download/:id", authFullUser, fileController.downloadFile); @@ -68,7 +75,9 @@ router.patch("/file-service/move", auth, fileController.moveFile); router.delete("/file-service/remove-link/:id", auth, fileController.removeLink); -router.delete("/file-service/remove/token-video/:tempToken/:uuid", auth, fileController.removeTempToken); +//router.delete("/file-service/remove/token-video/:tempToken/:uuid", auth, fileController.removeTempToken); + +router.delete("/file-service/remove/token-video/:id", auth, fileController.removeTempToken); router.delete("/file-service/remove", auth, fileController.deleteFile); diff --git a/backend/middleware/authStreamVideo.ts b/backend/middleware/authStreamVideo.ts new file mode 100644 index 0000000..dfc5196 --- /dev/null +++ b/backend/middleware/authStreamVideo.ts @@ -0,0 +1,96 @@ +import jwt from "jsonwebtoken"; +import User, {UserInterface} from "../models/user"; +import env from "../enviroment/env"; +import {Request, Response, NextFunction} from "express"; +import { ObjectID } from "mongodb"; + +interface RequestType extends Request { + user?: UserInterface, + token?: string, + encryptedToken?: string, +} + +// type jwtType = { +// iv: Buffer, +// user: userAccessType +// } + +type jwtType = { + iv: Buffer, + _id: string, + time: number +} + +type userAccessType = { + _id: string, + emailVerified: boolean, + email: string, + admin: boolean, + botChecked: boolean, + username: string, +} + +const removeOldTokens = async(userID: string, ipAddress: string | undefined, oldTime: number) => { + + try { + + const minusTime = oldTime - (60 * 1000 * 60 * 24); + + ipAddress = ipAddress ? ipAddress : ""; + + if (ipAddress === "") return; + + await User.updateOne({_id: userID}, {$pull: {tempTokens: {ipAddress, time: {$lt: minusTime}}}}) + + } catch (e) { + console.log("cannot remove old tokens", e); + } +} + + +const authStreamVideo = async(req: RequestType, res: Response, next: NextFunction) => { + + try { + + const accessTokenStreamVideo = req.cookies["video-access-token"]; + + if (!accessTokenStreamVideo) throw new Error("No Stream Video Access Token"); + + const decoded = jwt.verify(accessTokenStreamVideo, env.passwordAccess!) as jwtType; + + const time = decoded.time; + + const user = await User.findById(new ObjectID(decoded._id)); + + if (!user) throw new Error("No User Stream Video"); + + const encrpytionKey = user.getEncryptionKey(); + const encryptedToken = user.encryptToken(accessTokenStreamVideo, encrpytionKey, decoded.iv); + + let tokenFound = false; + + for (let i = 0; i < user.tempTokens.length; i++) { + + const currentEncryptedToken = user.tempTokens[i].token; + + if (currentEncryptedToken === encryptedToken) { + + tokenFound = true; + removeOldTokens(user._id, req.clientIp, time); + break; + } + } + + if (!tokenFound) throw new Error("Refresh Token Not Found"); + + req.user = user; + + next(); + + } catch (e) { + console.log("\nAuthorization Middleware Error:", e.message); + res.status(401).send("Error Authenticating"); + } +} + +export default authStreamVideo; diff --git a/backend/models/user.ts b/backend/models/user.ts index f1f0736..93ac254 100644 --- a/backend/models/user.ts +++ b/backend/models/user.ts @@ -51,6 +51,14 @@ const userSchema = new mongoose.Schema({ token: { type: String, required: true + }, + ipAddress: { + type: String, + required: true, + }, + time: { + type: Number, + required: true } }], privateKey: { @@ -187,11 +195,12 @@ export interface UserInterface extends Document { getEncryptionKey: () => Buffer | undefined; generateTempAuthToken: () => any; - generateTempAuthTokenVideo: (cookie: string) => any; + // generateTempAuthTokenVideo: (cookie: string) => any; encryptToken: (tempToken: any, key: any, publicKey: any) => any; decryptToken: (encryptedToken: any, key: any, publicKey: any) => any; findByCreds: (email: string, password: string) => UserInterface; generateAuthToken: (ipAddress: string | undefined) => Promise<{accessToken: string, refreshToken: string}> + generateAuthTokenStreamVideo: (ipAddress: string | undefined) => Promise generateEncryptionKeys: () => Promise; changeEncryptionKey: (randomKey: Buffer) => void; generateEmailVerifyToken: () => string; @@ -211,6 +220,8 @@ export interface UserInterface extends Document { const maxAgeAccess = 1000 * 5 * 10; //60 * 1000 * 20 + (1000 * 60); const maxAgeRefresh = 60 * 1000 * 60 * 24 * 30 + (1000 * 60); +const maxAgeAccessStreamVideo = 60 * 1000 * 60 * 24; + userSchema.pre("save", async function(this: any, next: any) { const user = this; @@ -254,6 +265,28 @@ userSchema.methods.toJSON = function() { return userObject; } +userSchema.methods.generateAuthTokenStreamVideo = async function(ipAddress: string | undefined) { + + const iv = crypto.randomBytes(16); + + const user = this; + + const date = new Date(); + const time = date.getTime(); + + let accessTokenStreamVideo = jwt.sign({_id:user._id.toString(), iv, time}, env.passwordAccess!, {expiresIn: maxAgeAccessStreamVideo.toString()}); + + const encryptionKey = user.getEncryptionKey(); + + const encryptedToken = user.encryptToken(accessTokenStreamVideo, encryptionKey, iv); + + ipAddress = ipAddress ? ipAddress : ""; + + await User.updateOne({_id: user._id}, {$push: {"tempTokens": {token: encryptedToken, ipAddress, time}}}); + + return accessTokenStreamVideo; +} + userSchema.methods.generateAuthToken = async function(ipAddress: string | undefined) { const iv = crypto.randomBytes(16); diff --git a/backend/services/FileService/index.ts b/backend/services/FileService/index.ts index a2d2581..cc99d56 100644 --- a/backend/services/FileService/index.ts +++ b/backend/services/FileService/index.ts @@ -161,17 +161,21 @@ class MongoFileService { return tempToken; } - getDownloadTokenVideo = async(user: UserInterface, cookie: string) => { - - if (!cookie) throw new NotAuthorizedError("Get Download Token Video Cookie Not Authorized Error"); - - const tempToken = await user.generateTempAuthTokenVideo(cookie); - - if (!tempToken) throw new NotAuthorizedError("Get Download Token Video Not Authorized Error"); - - return tempToken; + getAccessTokenStreamVideo = async() => { + } + // getDownloadTokenVideo = async(user: UserInterface, cookie: string) => { + + // if (!cookie) throw new NotAuthorizedError("Get Download Token Video Cookie Not Authorized Error"); + + // const tempToken = await user.generateTempAuthTokenVideo(cookie); + + // if (!tempToken) throw new NotAuthorizedError("Get Download Token Video Not Authorized Error"); + + // return tempToken; + // } + removeTempToken = async(user: UserInterface, tempToken: any, currentUUID: string) => { const key = user.getEncryptionKey(); diff --git a/src/components/PopupWindow/index.js b/src/components/PopupWindow/index.js index 987dac2..fb234e0 100644 --- a/src/components/PopupWindow/index.js +++ b/src/components/PopupWindow/index.js @@ -5,6 +5,7 @@ import env from "../../enviroment/envFrontEnd"; import {connect} from "react-redux"; import React from "react"; import { setPhotoID } from "../../actions/photoViewer"; +import axiosNonInterceptor from "axios"; const currentURL = env.url; @@ -94,36 +95,57 @@ class PopupWindowContainer extends React.Component { getVideo = () => { - const config = { - headers: { - uuid: window.sessionStorage.getItem("uuid") - } + // const config = { + // headers: { + // uuid: window.sessionStorage.getItem("uuid") + // } - }; + // }; + + axios.get("/file-service/download/access-token-stream-video").then(() => { - axios.get(currentURL +'/file-service/download/get-token-video',config) - .then((response) => { - - this.tempToken = response.data.tempToken; - - const uuidID = window.sessionStorage.getItem("uuid"); + console.log("stream video got token") const isDrive = this.props.popupFile.metadata.drive; const isPersonal = this.props.popupFile.metadata.personalFile; - + const finalUrl = isDrive ? - currentURL + `/file-service-google/stream-video/${this.props.popupFile._id}/${this.tempToken}/${uuidID}` - : !isPersonal ? currentURL + `/file-service/stream-video/${this.props.popupFile._id}/${this.tempToken}/${uuidID}` - : currentURL + `/file-service-personal/stream-video/${this.props.popupFile._id}/${this.tempToken}/${uuidID}` - + currentURL + `/file-service-google/stream-video/${this.props.popupFile._id}` + : !isPersonal ? currentURL + `/file-service/stream-video/${this.props.popupFile._id}` + : currentURL + `/file-service-personal/stream-video/${this.props.popupFile._id}` + this.setState(() => ({ ...this.state, video: finalUrl })) - }).catch((err) => { - console.log(err) + }).catch((e) => { + console.log("Stream Video Error", e.message); }) + + // axios.get(currentURL +'/file-service/download/get-token-video',config) + // .then((response) => { + + // this.tempToken = response.data.tempToken; + + // const uuidID = window.sessionStorage.getItem("uuid"); + + // const isDrive = this.props.popupFile.metadata.drive; + // const isPersonal = this.props.popupFile.metadata.personalFile; + + // const finalUrl = isDrive ? + // currentURL + `/file-service-google/stream-video/${this.props.popupFile._id}/${this.tempToken}/${uuidID}` + // : !isPersonal ? currentURL + `/file-service/stream-video/${this.props.popupFile._id}/${this.tempToken}/${uuidID}` + // : currentURL + `/file-service-personal/stream-video/${this.props.popupFile._id}/${this.tempToken}/${uuidID}` + + // this.setState(() => ({ + // ...this.state, + // video: finalUrl + // })) + + // }).catch((err) => { + // console.log(err) + // }) } componentWillUnmount = () => {