diff --git a/config.json b/config.json index 985642f..f2e8469 100644 --- a/config.json +++ b/config.json @@ -27,5 +27,5 @@ }, "trustProxy": true, - "forceSSL": true + "forceSSL": false } \ No newline at end of file diff --git a/src/server/index.ts b/src/server/index.ts index 68819e6..6baf6c6 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -8,13 +8,10 @@ import ServeError from "./lib/errors" import Files from "./lib/files" import * as auth from "./lib/auth" import * as Accounts from "./lib/accounts" - -import * as authRoutes from "./routes/authRoutes"; -import * as fileApiRoutes from "./routes/fileApiRoutes"; -import * as adminRoutes from "./routes/adminRoutes"; -import * as primaryApi from "./routes/primaryApi"; import { getAccount } from "./lib/middleware"; +import APIRouter from "./routes/api" + require("dotenv").config() let pkg = require(`${process.cwd()}/package.json`) @@ -46,19 +43,12 @@ app.get("/server",(req,res) => { })) }) -app - .use("/auth",authRoutes.authRoutes) - .use("/admin",adminRoutes.adminRoutes) - .use("/files", fileApiRoutes.fileApiRoutes) - .use(primaryApi.primaryApi) // funcs // init data if (!fs.existsSync(__dirname+"/../.data/")) fs.mkdirSync(__dirname+"/../.data/") - - // discord let client = new Client({intents:[ @@ -68,12 +58,11 @@ let client = new Client({intents:[ let files = new Files(client,config) -authRoutes.setFilesObj(files) -adminRoutes.setFilesObj(files) -fileApiRoutes.setFilesObj(files) -primaryApi.setFilesObj(files) - -// routes (could probably make these use routers) +let apiRouter = new APIRouter(files) +apiRouter.loadAPIMethods().then(() => { + app.use(apiRouter.root) + console.log("API OK!") +}) // index, clone diff --git a/src/server/routes/adminRoutes.ts b/src/server/routes/adminRoutes.ts deleted file mode 100644 index 510eec2..0000000 --- a/src/server/routes/adminRoutes.ts +++ /dev/null @@ -1,235 +0,0 @@ -import bodyParser from "body-parser"; -import { Router } from "express"; -import * as Accounts from "../lib/accounts"; -import * as auth from "../lib/auth"; -import bytes from "bytes" -import {writeFile} from "fs"; -import { sendMail } from "../lib/mail"; -import { getAccount, requiresAccount, requiresAdmin, requiresPermissions } from "../lib/middleware" - -import ServeError from "../lib/errors"; -import Files from "../lib/files"; - -let parser = bodyParser.json({ - type: ["text/plain","application/json"] -}) - -export let adminRoutes = Router(); -adminRoutes - .use(getAccount) - .use(requiresAccount) - .use(requiresAdmin) - .use(requiresPermissions("admin")) -let files:Files - -export function setFilesObj(newFiles:Files) { - files = newFiles -} - -let config = require(`${process.cwd()}/config.json`) - -adminRoutes.post("/reset", parser, (req,res) => { - - let acc = res.locals.acc as Accounts.Account - - if (typeof req.body.target !== "string" || typeof req.body.password !== "string") { - res.status(404) - res.send() - return - } - - let targetAccount = Accounts.getFromUsername(req.body.target) - if (!targetAccount) { - res.status(404) - res.send() - return - } - - Accounts.password.set ( targetAccount.id, req.body.password ) - auth.AuthTokens.filter(e => e.account == targetAccount?.id).forEach((v) => { - auth.invalidate(v.token) - }) - - if (targetAccount.email) { - sendMail(targetAccount.email, `Your login details have been updated`, `Hello there! This email is to notify you of a password change that an administrator, ${acc.username}, has initiated. You have been logged out of your devices. Thank you for using monofile.`).then(() => { - res.send("OK") - }).catch((err) => {}) - } - - - res.send() - -}) - -adminRoutes.post("/elevate", parser, (req,res) => { - - let acc = res.locals.acc as Accounts.Account - - if (typeof req.body.target !== "string") { - res.status(404) - res.send() - return - } - - let targetAccount = Accounts.getFromUsername(req.body.target) - if (!targetAccount) { - res.status(404) - res.send() - return - } - - targetAccount.admin = true; - Accounts.save() - res.send() - -}) - -adminRoutes.post("/delete", parser, (req,res) => { - - if (typeof req.body.target !== "string") { - res.status(404) - res.send() - return - } - - let targetFile = files.getFilePointer(req.body.target) - - if (!targetFile) { - res.status(404) - res.send() - return - } - - files.unlink(req.body.target).then(() => { - res.status(200) - }).catch(() => { - res.status(500) - }).finally(() => res.send()) - -}) - -adminRoutes.post("/delete_account", parser, async (req,res) => { - - let acc = res.locals.acc as Accounts.Account - - if (typeof req.body.target !== "string") { - res.status(404) - res.send() - return - } - - let targetAccount = Accounts.getFromUsername(req.body.target) - if (!targetAccount) { - res.status(404) - res.send() - return - } - - let accId = targetAccount.id - - auth.AuthTokens.filter(e => e.account == accId).forEach((v) => { - auth.invalidate(v.token) - }) - - let cpl = () => Accounts.deleteAccount(accId).then(_ => { - if (targetAccount?.email) { - sendMail(targetAccount.email, "Notice of account deletion", `Your account, ${targetAccount.username}, has been deleted by ${acc.username} for the following reason:

${req.body.reason || "(no reason specified)"}

Your files ${req.body.deleteFiles ? "have been deleted" : "have not been modified"}. Thank you for using monofile.`) - } - res.send("account deleted") - }) - - if (req.body.deleteFiles) { - let f = targetAccount.files.map(e=>e) // make shallow copy so that iterating over it doesnt Die - for (let v of f) { - files.unlink(v,true).catch(err => console.error(err)) - } - - writeFile(process.cwd()+"/.data/files.json",JSON.stringify(files.files), (err) => { - if (err) console.log(err) - cpl() - }) - } else cpl() -}) - -adminRoutes.post("/transfer", parser, (req,res) => { - - if (typeof req.body.target !== "string" || typeof req.body.owner !== "string") { - res.status(404) - res.send() - return - } - - let targetFile = files.getFilePointer(req.body.target) - if (!targetFile) { - res.status(404) - res.send() - return - } - - let newOwner = Accounts.getFromUsername(req.body.owner || "") - - // clear old owner - - if (targetFile.owner) { - let oldOwner = Accounts.getFromId(targetFile.owner) - if (oldOwner) { - Accounts.files.deindex(oldOwner.id, req.body.target) - } - } - - if (newOwner) { - Accounts.files.index(newOwner.id, req.body.target) - } - targetFile.owner = newOwner ? newOwner.id : undefined; - - files.writeFile(req.body.target, targetFile).then(() => { - res.send() - }).catch(() => { - res.status(500) - res.send() - }) // wasting a reassignment but whatee - -}) - -adminRoutes.post("/idchange", parser, (req,res) => { - - if (typeof req.body.target !== "string" || typeof req.body.new !== "string") { - res.status(400) - res.send() - return - } - - let targetFile = files.getFilePointer(req.body.target) - if (!targetFile) { - res.status(404) - res.send() - return - } - - if (files.getFilePointer(req.body.new)) { - res.status(400) - res.send() - return - } - - if (targetFile.owner) { - Accounts.files.deindex(targetFile.owner, req.body.target) - Accounts.files.index(targetFile.owner, req.body.new) - } - delete files.files[req.body.target] - - files.writeFile(req.body.new, targetFile).then(() => { - res.send() - }).catch(() => { - files.files[req.body.target] = req.body.new - - if (targetFile.owner) { - Accounts.files.deindex(targetFile.owner, req.body.new) - Accounts.files.index(targetFile.owner, req.body.target) - } - - res.status(500) - res.send() - }) - -}) \ No newline at end of file diff --git a/src/server/routes/api/v1/account.ts b/src/server/routes/api/v1/account.ts index e69de29..242a9f1 100644 --- a/src/server/routes/api/v1/account.ts +++ b/src/server/routes/api/v1/account.ts @@ -0,0 +1,13 @@ +import { Router } from "express"; +import Files from "../../../lib/files"; + +let router = Router() + +module.exports = function(files: Files) { + + router.get("/", function(req,res) { + res.send("hello world!") + }) + + return router +} \ No newline at end of file diff --git a/src/server/routes/api/v1/admin.ts b/src/server/routes/api/v1/admin.ts index e69de29..8c8168d 100644 --- a/src/server/routes/api/v1/admin.ts +++ b/src/server/routes/api/v1/admin.ts @@ -0,0 +1,8 @@ +import { Router } from "express"; +import Files from "../../../lib/files"; + +let router = Router() + +module.exports = function(files: Files) { + return router +} \ No newline at end of file diff --git a/src/server/routes/api/v1/file.ts b/src/server/routes/api/v1/file.ts index e69de29..8c8168d 100644 --- a/src/server/routes/api/v1/file.ts +++ b/src/server/routes/api/v1/file.ts @@ -0,0 +1,8 @@ +import { Router } from "express"; +import Files from "../../../lib/files"; + +let router = Router() + +module.exports = function(files: Files) { + return router +} \ No newline at end of file diff --git a/src/server/routes/api/v1/public.ts b/src/server/routes/api/v1/public.ts index e69de29..8c8168d 100644 --- a/src/server/routes/api/v1/public.ts +++ b/src/server/routes/api/v1/public.ts @@ -0,0 +1,8 @@ +import { Router } from "express"; +import Files from "../../../lib/files"; + +let router = Router() + +module.exports = function(files: Files) { + return router +} \ No newline at end of file diff --git a/src/server/routes/authRoutes.ts b/src/server/routes/authRoutes.ts deleted file mode 100644 index a8de6ff..0000000 --- a/src/server/routes/authRoutes.ts +++ /dev/null @@ -1,465 +0,0 @@ -import bodyParser from "body-parser"; -import { Router } from "express"; -import * as Accounts from "../lib/accounts"; -import * as auth from "../lib/auth"; -import { sendMail } from "../lib/mail"; -import { getAccount, noAPIAccess, requiresAccount, requiresPermissions } from "../lib/middleware" -import { accountRatelimit } from "../lib/ratelimit" - -import ServeError from "../lib/errors"; -import Files, { FileVisibility, generateFileId, id_check_regex } from "../lib/files"; - -import { writeFile } from "fs"; - -let parser = bodyParser.json({ - type: ["text/plain","application/json"] -}) - -export let authRoutes = Router(); -authRoutes.use(getAccount) - -let config = require(`${process.cwd()}/config.json`) - -let files:Files - -export function setFilesObj(newFiles:Files) { - files = newFiles -} - -authRoutes.post("/login", parser, (req,res) => { - if (typeof req.body.username != "string" || typeof req.body.password != "string") { - ServeError(res,400,"please provide a username or password") - return - } - - if (auth.validate(req.cookies.auth)) return - - /* - check if account exists - */ - - let acc = Accounts.getFromUsername(req.body.username) - - if (!acc) { - ServeError(res,401,"username or password incorrect") - return - } - - if (!Accounts.password.check(acc.id,req.body.password)) { - ServeError(res,401,"username or password incorrect") - return - } - - /* - assign token - */ - - res.cookie("auth",auth.create(acc.id,(3*24*60*60*1000))) - res.status(200) - res.end() -}) - -authRoutes.post("/create", parser, (req,res) => { - if (!config.accounts.registrationEnabled) { - ServeError(res,403,"account registration disabled") - return - } - - if (auth.validate(req.cookies.auth)) return - - if (typeof req.body.username != "string" || typeof req.body.password != "string") { - ServeError(res,400,"please provide a username or password") - return - } - - /* - check if account exists - */ - - let acc = Accounts.getFromUsername(req.body.username) - - if (acc) { - ServeError(res,400,"account with this username already exists") - return - } - - if (req.body.username.length < 3 || req.body.username.length > 20) { - ServeError(res,400,"username must be over or equal to 3 characters or under or equal to 20 characters in length") - return - } - - if ((req.body.username.match(/[A-Za-z0-9_\-\.]+/) || [])[0] != req.body.username) { - ServeError(res,400,"username contains invalid characters") - return - } - - if (req.body.password.length < 8) { - ServeError(res,400,"password must be 8 characters or longer") - return - } - - Accounts.create(req.body.username,req.body.password) - .then((newAcc) => { - /* - assign token - */ - - res.cookie("auth",auth.create(newAcc,(3*24*60*60*1000))) - res.status(200) - res.end() - }) - .catch(() => { - ServeError(res,500,"internal server error") - }) -}) - -authRoutes.post("/logout", (req,res) => { - if (!auth.validate(req.cookies.auth)) { - ServeError(res, 401, "not logged in") - return - } - - auth.invalidate(req.cookies.auth) - res.send("logged out") -}) - -authRoutes.post("/dfv", requiresAccount, requiresPermissions("manage"), parser, (req,res) => { - let acc = res.locals.acc as Accounts.Account - - if (['public','private','anonymous'].includes(req.body.defaultFileVisibility)) { - acc.defaultFileVisibility = req.body.defaultFileVisibility - Accounts.save() - res.send(`dfv has been set to ${acc.defaultFileVisibility}`) - } else { - res.status(400) - res.send("invalid dfv") - } -}) - -authRoutes.post("/customcss", requiresAccount, requiresPermissions("customize"), parser, (req,res) => { - let acc = res.locals.acc as Accounts.Account - - if (typeof req.body.fileId != "string") req.body.fileId = undefined; - - if ( - - !req.body.fileId - || (req.body.fileId.match(id_check_regex) == req.body.fileId - && req.body.fileId.length <= config.maxUploadIdLength) - - ) { - acc.customCSS = req.body.fileId || undefined - if (!req.body.fileId) delete acc.customCSS - Accounts.save() - res.send(`custom css saved`) - } else { - res.status(400) - res.send("invalid fileid") - } -}) - -authRoutes.post("/embedcolor", requiresAccount, requiresPermissions("customize"), parser, (req,res) => { - let acc = res.locals.acc as Accounts.Account - - if (typeof req.body.color != "string") req.body.color = undefined; - - if ( - - !req.body.color - || (req.body.color.toLowerCase().match(/[a-f0-9]+/) == req.body.color.toLowerCase()) - && req.body.color.length == 6 - - ) { - if (!acc.embed) acc.embed = {} - acc.embed.color = req.body.color || undefined - if (!req.body.color) delete acc.embed.color - Accounts.save() - res.send(`custom embed color saved`) - } else { - res.status(400) - res.send("invalid hex code") - } -}) - -authRoutes.post("/embedsize", requiresAccount, requiresPermissions("customize"), parser, (req,res) => { - let acc = res.locals.acc as Accounts.Account - - if (typeof req.body.largeImage != "boolean") req.body.color = false; - - if (!acc.embed) acc.embed = {} - acc.embed.largeImage = req.body.largeImage - if (!req.body.largeImage) delete acc.embed.largeImage - Accounts.save() - res.send(`custom embed image size saved`) -}) - -authRoutes.post("/delete_account", requiresAccount, noAPIAccess, parser, async (req,res) => { - let acc = res.locals.acc as Accounts.Account - - let accId = acc.id - - auth.AuthTokens.filter(e => e.account == accId).forEach((v) => { - auth.invalidate(v.token) - }) - - let cpl = () => Accounts.deleteAccount(accId).then(_ => res.send("account deleted")) - - if (req.body.deleteFiles) { - let f = acc.files.map(e=>e) // make shallow copy so that iterating over it doesnt Die - for (let v of f) { - files.unlink(v,true).catch(err => console.error(err)) - } - - writeFile(process.cwd()+"/.data/files.json",JSON.stringify(files.files), (err) => { - if (err) console.log(err) - cpl() - }) - } else cpl() -}) - -authRoutes.post("/change_username", requiresAccount, noAPIAccess, parser, (req,res) => { - let acc = res.locals.acc as Accounts.Account - - if (typeof req.body.username != "string" || req.body.username.length < 3 || req.body.username.length > 20) { - ServeError(res,400,"username must be between 3 and 20 characters in length") - return - } - - let _acc = Accounts.getFromUsername(req.body.username) - - if (_acc) { - ServeError(res,400,"account with this username already exists") - return - } - - if ((req.body.username.match(/[A-Za-z0-9_\-\.]+/) || [])[0] != req.body.username) { - ServeError(res,400,"username contains invalid characters") - return - } - - acc.username = req.body.username - Accounts.save() - - if (acc.email) { - sendMail(acc.email, `Your login details have been updated`, `Hello there! Your username has been updated to ${req.body.username}. Please update your devices accordingly. Thank you for using monofile.`).then(() => { - res.send("OK") - }).catch((err) => {}) - } - - res.send("username changed") -}) - -// shit way to do this but... - -let verificationCodes = new Map() - -authRoutes.post("/request_email_change", requiresAccount, noAPIAccess, accountRatelimit({ requests: 4, per: 60*60*1000 }), parser, (req,res) => { - let acc = res.locals.acc as Accounts.Account - - - if (typeof req.body.email != "string" || !req.body.email) { - ServeError(res,400, "supply an email") - return - } - - let vcode = verificationCodes.get(acc.id) - - // delete previous if any - let e = vcode?.expiry - if (e) clearTimeout(e) - verificationCodes.delete(acc?.id||"") - - let code = generateFileId(12).toUpperCase() - - // set - - verificationCodes.set(acc.id, { - code, - email: req.body.email, - expiry: setTimeout( () => verificationCodes.delete(acc?.id||""), 15*60*1000) - }) - - // this is a mess but it's fine - - sendMail(req.body.email, `Hey there, ${acc.username} - let's connect your email`, `Hello there! You are recieving this message because you decided to link your email, ${req.body.email.split("@")[0]}@${req.body.email.split("@")[1]}, to your account, ${acc.username}. If you would like to continue, please click here, or go to https://${req.header("Host")}/auth/confirm_email/${code}.`).then(() => { - res.send("OK") - }).catch((err) => { - let e = verificationCodes.get(acc?.id||"")?.expiry - if (e) clearTimeout(e) - verificationCodes.delete(acc?.id||"") - res.locals.undoCount(); - ServeError(res, 500, err?.toString()) - }) -}) - -authRoutes.get("/confirm_email/:code", requiresAccount, noAPIAccess, (req,res) => { - let acc = res.locals.acc as Accounts.Account - - - let vcode = verificationCodes.get(acc.id) - - if (!vcode) { ServeError(res, 400, "nothing to confirm"); return } - - if (typeof req.params.code == "string" && req.params.code.toUpperCase() == vcode.code) { - acc.email = vcode.email - Accounts.save(); - - let e = verificationCodes.get(acc?.id||"")?.expiry - if (e) clearTimeout(e) - verificationCodes.delete(acc?.id||"") - - res.redirect("/") - } else { - ServeError(res, 400, "invalid code") - } -}) - -authRoutes.post("/remove_email", requiresAccount, noAPIAccess, (req,res) => { - let acc = res.locals.acc as Accounts.Account - - if (acc.email) { - delete acc.email; - Accounts.save() - res.send("email detached") - } - else ServeError(res, 400, "email not attached") -}) - -let pwReset = new Map() -let prcIdx = new Map() - -authRoutes.post("/request_emergency_login", parser, (req,res) => { - if (auth.validate(req.cookies.auth || "")) return - - if (typeof req.body.account != "string" || !req.body.account) { - ServeError(res,400, "supply a username") - return - } - - let acc = Accounts.getFromUsername(req.body.account) - if (!acc || !acc.email) { - ServeError(res, 400, "this account either does not exist or does not have an email attached; please contact the server's admin for a reset if you would still like to access it") - return - } - - let pResetCode = pwReset.get(acc.id) - - if (pResetCode && pResetCode.requestedAt+(15*60*1000) > Date.now()) { - ServeError(res, 429, `Please wait a few moments to request another emergency login.`) - return - } - - - // delete previous if any - let e = pResetCode?.expiry - if (e) clearTimeout(e) - pwReset.delete(acc?.id||"") - prcIdx.delete(pResetCode?.code||"") - - let code = generateFileId(12).toUpperCase() - - // set - - pwReset.set(acc.id, { - code, - expiry: setTimeout( () => { pwReset.delete(acc?.id||""); prcIdx.delete(pResetCode?.code||"") }, 15*60*1000), - requestedAt: Date.now() - }) - - prcIdx.set(code, acc.id) - - // this is a mess but it's fine - - sendMail(acc.email, `Emergency login requested for ${acc.username}`, `Hello there! You are recieving this message because you forgot your password to your monofile account, ${acc.username}. To log in, please click here, or go to https://${req.header("Host")}/auth/emergency_login/${code}. If it doesn't appear that you are logged in after visiting this link, please try refreshing. Once you have successfully logged in, you may reset your password.`).then(() => { - res.send("OK") - }).catch((err) => { - let e = pwReset.get(acc?.id||"")?.expiry - if (e) clearTimeout(e) - pwReset.delete(acc?.id||"") - prcIdx.delete(code||"") - ServeError(res, 500, err?.toString()) - }) -}) - -authRoutes.get("/emergency_login/:code", (req,res) => { - if (auth.validate(req.cookies.auth || "")) { - ServeError(res, 403, "already logged in") - return - } - - let vcode = prcIdx.get(req.params.code) - - if (!vcode) { ServeError(res, 400, "invalid emergency login code"); return } - - if (typeof req.params.code == "string" && vcode) { - res.cookie("auth",auth.create(vcode,(3*24*60*60*1000))) - res.redirect("/") - - let e = pwReset.get(vcode)?.expiry - if (e) clearTimeout(e) - pwReset.delete(vcode) - prcIdx.delete(req.params.code) - } else { - ServeError(res, 400, "invalid code") - } -}) - -authRoutes.post("/change_password", requiresAccount, noAPIAccess, parser, (req,res) => { - let acc = res.locals.acc as Accounts.Account - - if (typeof req.body.password != "string" || req.body.password.length < 8) { - ServeError(res,400,"password must be 8 characters or longer") - return - } - - let accId = acc.id - - Accounts.password.set(accId,req.body.password) - - auth.AuthTokens.filter(e => e.account == accId).forEach((v) => { - auth.invalidate(v.token) - }) - - if (acc.email) { - sendMail(acc.email, `Your login details have been updated`, `Hello there! This email is to notify you of a password change that you have initiated. You have been logged out of your devices. Thank you for using monofile.`).then(() => { - res.send("OK") - }).catch((err) => {}) - } - - res.send("password changed - logged out all sessions") -}) - -authRoutes.post("/logout_sessions", requiresAccount, noAPIAccess, (req,res) => { - let acc = res.locals.acc as Accounts.Account - - let accId = acc.id - - auth.AuthTokens.filter(e => e.account == accId).forEach((v) => { - auth.invalidate(v.token) - }) - - res.send("logged out all sessions") -}) - -authRoutes.get("/me", requiresAccount, requiresPermissions("user"), (req,res) => { - let acc = res.locals.acc as Accounts.Account - - let sessionToken = auth.tokenFor(req) - let accId = acc.id - res.send({ - ...acc, - sessionCount: auth.AuthTokens.filter(e => e.type != "App" && e.account == accId && (e.expire > Date.now() || !e.expire)).length, - sessionExpires: auth.AuthTokens.find(e => e.token == sessionToken)?.expire, - password: undefined, - email: - auth.getType(sessionToken) == "User" || auth.getPermissions(sessionToken)?.includes("email") - ? acc.email - : undefined - }) -}) - -authRoutes.get("/customCSS", (req,res) => { - let acc = res.locals.acc - if (acc?.customCSS) res.redirect(`/file/${acc.customCSS}`) - else res.send("") -}) diff --git a/src/server/routes/fileApiRoutes.ts b/src/server/routes/fileApiRoutes.ts deleted file mode 100644 index f64d141..0000000 --- a/src/server/routes/fileApiRoutes.ts +++ /dev/null @@ -1,97 +0,0 @@ -import bodyParser from "body-parser"; -import { Router } from "express"; -import * as Accounts from "../lib/accounts"; -import * as auth from "../lib/auth"; -import bytes from "bytes" -import {writeFile} from "fs"; - -import ServeError from "../lib/errors"; -import Files from "../lib/files"; -import { getAccount, requiresAccount, requiresPermissions } from "../lib/middleware"; - -let parser = bodyParser.json({ - type: ["text/plain","application/json"] -}) - -export let fileApiRoutes = Router(); -let files:Files - -export function setFilesObj(newFiles:Files) { - files = newFiles -} - -let config = require(`${process.cwd()}/config.json`) - -fileApiRoutes.use(getAccount); - -fileApiRoutes.get("/list", requiresAccount, requiresPermissions("user"), (req,res) => { - - let acc = res.locals.acc as Accounts.Account - - if (!acc) return - let accId = acc.id - - res.send(acc.files.map((e) => { - let fp = files.getFilePointer(e) - if (!fp) { Accounts.files.deindex(accId, e); return null } - return { - ...fp, - messageids: null, - owner: null, - id:e - } - }).filter(e=>e)) - -}) - -fileApiRoutes.post("/manage", parser, requiresPermissions("manage"), (req,res) => { - - let acc = res.locals.acc as Accounts.Account - - if (!acc) return - if (!req.body.target || !(typeof req.body.target == "object") || req.body.target.length < 1) return - - let modified = 0 - - req.body.target.forEach((e:string) => { - if (!acc.files.includes(e)) return - - let fp = files.getFilePointer(e) - - if (fp.reserved) { - return - } - - switch( req.body.action ) { - case "delete": - files.unlink(e, true) - modified++; - break; - - case "changeFileVisibility": - if (!["public","anonymous","private"].includes(req.body.value)) return; - files.files[e].visibility = req.body.value; - modified++; - break; - - case "setTag": - if (!req.body.value) delete files.files[e].tag - else { - if (req.body.value.toString().length > 30) return - files.files[e].tag = req.body.value.toString().toLowerCase() - } - modified++; - break; - } - }) - - Accounts.save().then(() => { - writeFile(process.cwd()+"/.data/files.json",JSON.stringify(files.files), (err) => { - if (err) console.log(err) - res.contentType("text/plain") - res.send(`modified ${modified} files`) - }) - }).catch((err) => console.error(err)) - - -}) \ No newline at end of file diff --git a/src/server/routes/primaryApi.ts b/src/server/routes/primaryApi.ts deleted file mode 100644 index 33558d8..0000000 --- a/src/server/routes/primaryApi.ts +++ /dev/null @@ -1,181 +0,0 @@ -import bodyParser from "body-parser"; -import express, { Router } from "express"; -import * as Accounts from "../lib/accounts"; -import * as auth from "../lib/auth"; -import axios, { AxiosResponse } from "axios" -import { type Range } from "range-parser"; -import multer, {memoryStorage} from "multer" - -import ServeError from "../lib/errors"; -import Files from "../lib/files"; -import { getAccount, requiresPermissions } from "../lib/middleware"; - -let parser = bodyParser.json({ - type: ["text/plain","application/json"] -}) - -export let primaryApi = Router(); -let files:Files - -export function setFilesObj(newFiles:Files) { - files = newFiles -} - -const multerSetup = multer({storage:memoryStorage()}) - -let config = require(`${process.cwd()}/config.json`) - -primaryApi.use(getAccount); - -primaryApi.get(["/file/:fileId", "/cpt/:fileId/*", "/:fileId"], async (req:express.Request,res:express.Response) => { - - let acc = res.locals.acc as Accounts.Account - - let file = files.getFilePointer(req.params.fileId) - res.setHeader("Access-Control-Allow-Origin", "*") - res.setHeader("Content-Security-Policy","sandbox allow-scripts") - if (req.query.attachment == "1") res.setHeader("Content-Disposition", "attachment") - - if (file) { - - if (file.visibility == "private" && acc?.id != file.owner) { - ServeError(res,403,"you do not own this file") - return - } - - let range: Range | undefined - - res.setHeader("Content-Type",file.mime) - if (file.sizeInBytes) { - res.setHeader("Content-Length",file.sizeInBytes) - - if (file.chunkSize) { - let rng = req.range(file.sizeInBytes) - if (rng) { - - // error handling - if (typeof rng == "number") { - res.status(rng == -1 ? 416 : 400).send() - return - } - if (rng.type != "bytes") { - res.status(400).send(); - return - } - - // set ranges var - let rngs = Array.from(rng) - if (rngs.length != 1) { res.status(400).send(); return } - range = rngs[0] - - } - } - } - - // supports ranges - - - files.readFileStream(req.params.fileId, range).then(async stream => { - - if (range) { - res.status(206) - res.header("Content-Length", (range.end-range.start + 1).toString()) - res.header("Content-Range", `bytes ${range.start}-${range.end}/${file.sizeInBytes}`) - } - stream.pipe(res) - - }).catch((err) => { - ServeError(res,err.status,err.message) - }) - - } else { - ServeError(res, 404, "file not found") - } - -}) - -primaryApi.head(["/file/:fileId", "/cpt/:fileId/*", "/:fileId"], (req: express.Request, res:express.Response) => { - let file = files.getFilePointer(req.params.fileId) - res.setHeader("Access-Control-Allow-Origin", "*") - res.setHeader("Content-Security-Policy","sandbox allow-scripts") - if (req.query.attachment == "1") res.setHeader("Content-Disposition", "attachment") - if (!file) { - res.status(404) - res.send() - } else { - res.setHeader("Content-Type",file.mime) - if (file.sizeInBytes) { - res.setHeader("Content-Length",file.sizeInBytes) - } - if (file.chunkSize) { - res.setHeader("Accept-Ranges", "bytes") - } - } -}) - -// upload handlers - -primaryApi.post("/upload", requiresPermissions("upload"), multerSetup.single('file'), async (req,res) => { - - let acc = res.locals.acc as Accounts.Account - - if (req.file) { - try { - let prm = req.header("monofile-params") - let params:{[key:string]:any} = {} - if (prm) { - params = JSON.parse(prm) - } - - files.uploadFile({ - owner: acc?.id, - - uploadId:params.uploadId, - name:req.file.originalname, - mime:req.file.mimetype - },req.file.buffer) - .then((uID) => res.send(uID)) - .catch((stat) => { - res.status(stat.status); - res.send(`[err] ${stat.message}`) - }) - } catch { - res.status(400) - res.send("[err] bad request") - } - } else { - res.status(400) - res.send("[err] bad request") - } -}) - -primaryApi.post("/clone", requiresPermissions("upload"), bodyParser.json({type: ["text/plain","application/json"]}) ,(req,res) => { - - let acc = res.locals.acc as Accounts.Account - - try { - axios.get(req.body.url,{responseType:"arraybuffer"}).then((data:AxiosResponse) => { - - files.uploadFile({ - owner: acc?.id, - - name:req.body.url.split("/")[req.body.url.split("/").length-1] || "generic", - mime:data.headers["content-type"], - uploadId:req.body.uploadId - },Buffer.from(data.data)) - .then((uID) => res.send(uID)) - .catch((stat) => { - res.status(stat.status); - res.send(`[err] ${stat.message}`) - }) - - }).catch((err) => { - console.log(err) - res.status(400) - res.send(`[err] failed to fetch data`) - }) - } catch { - res.status(500) - res.send("[err] an error occured") - } -}) \ No newline at end of file