diff --git a/backend/app.js b/backend/app.js index 263a965..f2a4939 100644 --- a/backend/app.js +++ b/backend/app.js @@ -12,6 +12,7 @@ const authRoutes = require("./routes/auth"); const networkRoutes = require("./routes/network"); const memberRoutes = require("./routes/member"); const controllerRoutes = require("./routes/controller"); +const downFileRoutes = require("./routes/downfile"); const app = express(); @@ -64,6 +65,7 @@ routerController.use("", controllerRoutes); app.use("/auth", authRoutes); app.use("/api", routerAPI); // offical SaaS API compatible app.use("/controller", routerController); // other controller-specific routes +app.use("/downfile", downFileRoutes); // file download routes // error handlers app.get("*", async function (req, res) { diff --git a/backend/routes/downfile.js b/backend/routes/downfile.js new file mode 100644 index 0000000..a82e1cb --- /dev/null +++ b/backend/routes/downfile.js @@ -0,0 +1,21 @@ +const express = require("express"); +const path = require("path"); +const router = express.Router(); + +const auth = require("../services/auth"); + +router.get("/:downfilename", async function (req, res) { + const ret = await auth.isUserLoggedIn(req); + if (ret) { + const filename = req.params.downfilename; + res.sendFile( + path.join(__dirname, "..", "..", "frontend", "down_folder", filename) + ); + } else { + res + .status(401) + .json({ error: "401 Not authorized, must Login to download" }); + } +}); + +module.exports = router; diff --git a/backend/services/auth.js b/backend/services/auth.js index 9833857..35c3c5f 100644 --- a/backend/services/auth.js +++ b/backend/services/auth.js @@ -35,3 +35,16 @@ async function isAuthorized(req, res, next) { } } } + +exports.isUserLoggedIn = isUserLoggedIn; +async function isUserLoggedIn(req) { + if (process.env.ZU_DISABLE_AUTH === "true") { + // assuming logged in + return true; + } + if (!req.token) { + return false; + } + const user = await db.get("users").find({ token: req.token }).value(); + return !!user ? true : false; +} diff --git a/frontend/down_folder/.keep b/frontend/down_folder/.keep new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/components/HomeLoggedIn/HomeLoggedIn.jsx b/frontend/src/components/HomeLoggedIn/HomeLoggedIn.jsx index ff45f76..8d79365 100644 --- a/frontend/src/components/HomeLoggedIn/HomeLoggedIn.jsx +++ b/frontend/src/components/HomeLoggedIn/HomeLoggedIn.jsx @@ -5,6 +5,7 @@ import { Divider, Button, Grid, Typography, Box } from "@material-ui/core"; import useStyles from "./HomeLoggedIn.styles"; import NetworkButton from "./components/NetworkButton"; +import DownloadFile from "./components/DownloadFile"; import API from "utils/API"; import { generateNetworkConfig } from "utils/NetworkConfig"; @@ -40,6 +41,8 @@ function HomeLoggedIn() { > Create A Network + + diff --git a/frontend/src/components/HomeLoggedIn/components/DownloadFile/DownloadFile.jsx b/frontend/src/components/HomeLoggedIn/components/DownloadFile/DownloadFile.jsx new file mode 100644 index 0000000..83831b2 --- /dev/null +++ b/frontend/src/components/HomeLoggedIn/components/DownloadFile/DownloadFile.jsx @@ -0,0 +1,107 @@ +import { useState } from "react"; +import { useLocalStorage } from "react-use"; +import { TextField, Button, Snackbar } from "@material-ui/core"; + +import axios from "axios"; + +function DownloadFile() { + const [snackbarOpen, setSnackbarOpen] = useState(false); + + const [filename, setFilename] = useState("planet"); + const [errormsg, setErrormsg] = useState(""); + + const [token] = useLocalStorage("token", null); + + const downloadFile = async (fileName) => { + if (!!!token) { + await handleDownFileErr(`Invalid token`); + return; + } + if (typeof filename === "undefined" || filename.length === 0) { + await handleDownFileErr(`Invalid file name [${filename}]`); + return; + } + let ret; + ret = await axios + .create({ + baseURL: `/downfile/`, + responseType: "arraybuffer", + withCredentials: "true", + headers: + localStorage.getItem("disableAuth") === "true" + ? {} + : { + Authorization: `Bearer ${token}`, + }, + }) + .get(`${fileName}`) + .then((resp) => { + const blobUrl = window.URL.createObjectURL( + new Blob([resp.data], { type: "application/octet-stream" }) + ); + const tmpLink = document.createElement("a"); + tmpLink.style.display = "none"; + tmpLink.href = blobUrl; + tmpLink.setAttribute("download", `${fileName}`); + if (typeof tmpLink.download === "undefined") { + tmpLink.setAttribute("target", "_blank"); + } + document.body.appendChild(tmpLink); + tmpLink.click(); + document.body.removeChild(tmpLink); + window.URL.revokeObjectURL(blobUrl); + }) + .catch(async (e) => { + let errmsg = `downfile(${fileName}): ${e}: ${ret}`; + await handleDownFileErr(errmsg); + }); + }; + + const handleDownFileBtnClick = async () => { + await downloadFile(filename); + }; + + const handleDownFileErr = async (errormsg) => { + setErrormsg(errormsg); + setSnackbarOpen(true); + let sleep = function (ms) { + return new Promise((resolve) => { + setTimeout(resolve, ms); + }); + }; + await sleep(3 * 1000); + setSnackbarOpen(false); + setErrormsg(""); + }; + + return ( + <> + { + setFilename(e.target.value); + }} + margin="dense" + label="File name to download" + type="text" + /> + + + + ); +} + +export default DownloadFile; diff --git a/frontend/src/components/HomeLoggedIn/components/DownloadFile/index.jsx b/frontend/src/components/HomeLoggedIn/components/DownloadFile/index.jsx new file mode 100644 index 0000000..e720e16 --- /dev/null +++ b/frontend/src/components/HomeLoggedIn/components/DownloadFile/index.jsx @@ -0,0 +1 @@ +export { default } from "./DownloadFile";