From 86f5727d831a33efd42d1c193aff58fcf4a98525 Mon Sep 17 00:00:00 2001
From: stringsplit <77242831+nbitzz@users.noreply.github.com>
Date: Fri, 8 Mar 2024 20:02:27 -0800
Subject: [PATCH] Okay that's enough for now
---
src/download.html | 2 +-
src/error.html | 2 +-
src/index.html | 2 +-
src/server/index.ts | 6 +-
src/server/lib/mail.ts | 4 +-
src/server/routes/api.ts | 1 -
src/server/routes/api/v0/adminRoutes.ts | 37 +++---
src/server/routes/api/v0/primaryApi.ts | 1 -
src/server/routes/api/v1/account.ts | 148 ++++++++++++++++-----
src/server/routes/api/v1/session.ts | 10 ++
src/server/routes/api/web/api.json | 7 +
src/server/routes/{ => api/web}/preview.ts | 24 ++--
12 files changed, 173 insertions(+), 71 deletions(-)
create mode 100644 src/server/routes/api/web/api.json
rename src/server/routes/{ => api/web}/preview.ts (91%)
diff --git a/src/download.html b/src/download.html
index 80a2093..dd0847a 100644
--- a/src/download.html
+++ b/src/download.html
@@ -19,7 +19,7 @@
-
+
)
)
-// serve download page
-
-app.get("/download/:fileId", getAccount, preview(files))
-
/*
routes should be in this order:
diff --git a/src/server/lib/mail.ts b/src/server/lib/mail.ts
index 647982b..7f7f266 100644
--- a/src/server/lib/mail.ts
+++ b/src/server/lib/mail.ts
@@ -11,8 +11,6 @@ let mailConfig = config.mail,
},
})
-// lazy but
-
/**
* @description Sends an email
* @param to Target email address
@@ -35,4 +33,4 @@ export function sendMail(to: string, subject: string, content: string) {
``
)}
If you do not believe that you are the intended recipient of this email, please disregard this message.`,
})
-}
+}
\ No newline at end of file
diff --git a/src/server/routes/api.ts b/src/server/routes/api.ts
index 0d5ebe1..ab4ca43 100644
--- a/src/server/routes/api.ts
+++ b/src/server/routes/api.ts
@@ -74,7 +74,6 @@ export default class APIRouter {
async loadAPIMethods() {
let files = await readdir(APIDirectory)
for (let version of files) {
- /// temporary (hopefully). need to figure out something else for this
let def = JSON.parse(
(
await readFile(
diff --git a/src/server/routes/api/v0/adminRoutes.ts b/src/server/routes/api/v0/adminRoutes.ts
index 4e561d7..d251b1f 100644
--- a/src/server/routes/api/v0/adminRoutes.ts
+++ b/src/server/routes/api/v0/adminRoutes.ts
@@ -31,12 +31,12 @@ export default function (files: Files) {
typeof body.target !== "string" ||
typeof body.password !== "string"
) {
- return ctx.status(404)
+ return ctx.text("not found", 404)
}
let targetAccount = Accounts.getFromUsername(body.target)
if (!targetAccount) {
- return ctx.status(404)
+ return ctx.text("not found", 404)
}
Accounts.password.set(targetAccount.id, body.password)
@@ -53,7 +53,7 @@ export default function (files: Files) {
`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(() => ctx.text("OK"))
- .catch(() => ctx.status(500))
+ .catch(() => ctx.text("err while sending email", 500))
}
})
@@ -62,12 +62,12 @@ export default function (files: Files) {
let acc = ctx.get("account") as Accounts.Account
if (typeof body.target !== "string") {
- return ctx.status(404)
+ return ctx.text("not found", 404)
}
let targetAccount = Accounts.getFromUsername(body.target)
if (!targetAccount) {
- return ctx.status(404)
+ return ctx.text("not found", 404)
}
Accounts.save()
@@ -77,32 +77,31 @@ export default function (files: Files) {
adminRoutes.post("/delete", async (ctx) => {
const body = await ctx.req.json()
if (typeof body.target !== "string") {
- return ctx.status(404)
+ return ctx.text("not found", 404)
}
let targetFile = files.files[body.target]
if (!targetFile) {
- return ctx.status(404)
+ return ctx.text("not found", 404)
}
return files
.unlink(body.target)
- .then(() => ctx.status(200))
- .catch(() => ctx.status(500))
- .finally(() => ctx.status(200))
+ .then(() => ctx.text("ok", 200))
+ .catch(() => ctx.text("err", 500))
})
adminRoutes.post("/delete_account", async (ctx) => {
let acc = ctx.get("account") as Accounts.Account
const body = await ctx.req.json()
if (typeof body.target !== "string") {
- return ctx.status(404)
+ return ctx.text("not found", 404)
}
let targetAccount = Accounts.getFromUsername(body.target)
if (!targetAccount) {
- return ctx.status(404)
+ return ctx.text("not found", 404)
}
let accId = targetAccount.id
@@ -149,12 +148,12 @@ export default function (files: Files) {
adminRoutes.post("/transfer", async (ctx) => {
const body = await ctx.req.json()
if (typeof body.target !== "string" || typeof body.owner !== "string") {
- return ctx.status(404)
+ return ctx.text("not found", 404)
}
let targetFile = files.files[body.target]
if (!targetFile) {
- return ctx.status(404)
+ return ctx.text("not found", 404)
}
let newOwner = Accounts.getFromUsername(body.owner || "")
@@ -173,21 +172,21 @@ export default function (files: Files) {
}
targetFile.owner = newOwner ? newOwner.id : undefined
- files
+ return files
.write()
- .then(() => ctx.status(200))
- .catch(() => ctx.status(500))
+ .then(() => ctx.text("ok", 200))
+ .catch(() => ctx.text("error", 500))
})
adminRoutes.post("/idchange", async (ctx) => {
const body = await ctx.req.json()
if (typeof body.target !== "string" || typeof body.new !== "string") {
- return ctx.status(400)
+ return ctx.text("inappropriate body", 400)
}
let targetFile = files.files[body.target]
if (!targetFile) {
- return ctx.status(404)
+ return ctx.text("not found", 404)
}
if (files.files[body.new]) {
diff --git a/src/server/routes/api/v0/primaryApi.ts b/src/server/routes/api/v0/primaryApi.ts
index b44b717..4bb3726 100644
--- a/src/server/routes/api/v0/primaryApi.ts
+++ b/src/server/routes/api/v0/primaryApi.ts
@@ -105,7 +105,6 @@ export default function (files: Files) {
}
}
)
- // upload handlers
primaryApi.post(
"/upload",
diff --git a/src/server/routes/api/v1/account.ts b/src/server/routes/api/v1/account.ts
index cf3b36a..5b2da93 100644
--- a/src/server/routes/api/v1/account.ts
+++ b/src/server/routes/api/v1/account.ts
@@ -33,28 +33,54 @@ type Message = [200 | 400 | 401 | 403 | 501, string]
// there's probably a less stupid way to do this than `K in keyof Pick`
// @Jack5079 make typings better if possible
+
+type Validator, ValueNotNull extends boolean> =
+ /**
+ * @param actor The account performing this action
+ * @param target The target account for this action
+ * @param params Changes being patched in by the user
+ */
+ (actor: Accounts.Account, target: Accounts.Account, params: UserUpdateParameters & (ValueNotNull extends true ? {
+ [K in keyof Pick]-? : UserUpdateParameters[K]
+ } : {})) => Accounts.Account[T] | Message
+
+// this type is so stupid stg
+interface ValidatorWithSettings> {
+ acceptsNull?: boolean,
+ validator: Validator // i give upp ill fix this later
+}
+
const validators: {
[T in keyof Partial]:
- /**
- * @param actor The account performing this action
- * @param target The target account for this action
- * @param params Changes being patched in by the user
- */
- (actor: Accounts.Account, target: Accounts.Account, params: UserUpdateParameters & {
- [K in keyof Pick]-? : UserUpdateParameters[K]
- }) => Accounts.Account[T] | Message
+ Validator | ValidatorWithSettings
} = {
defaultFileVisibility(actor, target, params) {
if (["public", "private", "anonymous"].includes(params.defaultFileVisibility))
return params.defaultFileVisibility
else return [400, "invalid file visibility"]
},
- email(actor, target, params) {
- return [501, "not implemented"]
+ email: {
+ acceptsNull: true,
+ validator: (actor, target, params) => {
+ if (!params.currentPassword // actor on purpose here to allow admins
+ || (params.currentPassword && Accounts.password.check(actor.id, params.currentPassword)))
+ return [401, "current password incorrect"]
+
+ if (!params.email) {
+ if (target.email) {
+ sendMail(
+ target.email,
+ `Email disconnected`,
+ `Hello there! Your email address (${target.email}) has been disconnected from the monofile account ${target.username}. Thank you for using monofile.`
+ ).catch()
+ }
+ return undefined
+ }
+ }
},
password(actor, target, params) {
if (
- !params.currentPassword
+ !params.currentPassword // actor on purpose here to allow admins
|| (params.currentPassword && Accounts.password.check(actor.id, params.currentPassword))
) return [401, "current password incorrect"]
@@ -77,7 +103,7 @@ const validators: {
},
username(actor, target, params) {
- if (!params.currentPassword
+ if (!params.currentPassword // actor on purpose here to allow admins
|| (params.currentPassword && Accounts.password.check(actor.id, params.currentPassword)))
return [401, "current password incorrect"]
@@ -106,13 +132,30 @@ const validators: {
return params.username
},
- customCSS(actor, target, params) {
- if (
- !params.customCSS ||
- (params.customCSS.match(id_check_regex)?.[0] == params.customCSS &&
- params.customCSS.length <= Configuration.maxUploadIdLength)
- ) return params.customCSS
- else return [400, "bad file id"]
+ customCSS: {
+ acceptsNull: true,
+ validator: (actor, target, params) => {
+ if (
+ !params.customCSS ||
+ (params.customCSS.match(id_check_regex)?.[0] == params.customCSS &&
+ params.customCSS.length <= Configuration.maxUploadIdLength)
+ ) return params.customCSS
+ else return [400, "bad file id"]
+ }
+ },
+ embed(actor, target, params) {
+ if (params.embed.color === undefined) {
+ params.embed.color = target.embed?.color
+ } else if (!((params.embed.color.toLowerCase().match(/[a-f0-9]+/)?.[0] ==
+ params.embed.color.toLowerCase() &&
+ params.embed.color.length == 6) || params.embed.color == null)) return [400, "bad embed color"]
+
+
+ if (params.embed.largeImage === undefined) {
+ params.embed.largeImage = target.embed?.largeImage
+ } else params.embed.largeImage = Boolean(params.embed.largeImage)
+
+ return params.embed
},
admin(actor, target, params) {
if (actor.admin && !target.admin) return params.admin
@@ -217,24 +260,41 @@ export default function (files: Files) {
if (Array.isArray(body))
return ServeError(ctx, 400, "invalid body")
- let results: [keyof Accounts.Account, Accounts.Account[keyof Accounts.Account]|Message][] = Object.entries(body).filter(e => e[1] && e[0] !== "currentPassword").map(([x]) =>
- [
- x as keyof Accounts.Account,
- x in validators
- ? validators[x as keyof Accounts.Account]!(actor, target, body as any)
- : [400, `the ${x} parameter cannot be set or is not a valid parameter`] as Message
- ]
- )
+ let results: ([keyof Accounts.Account, Accounts.Account[keyof Accounts.Account]]|Message)[] =
+ (Object.entries(body)
+ .filter(e => e[0] !== "currentPassword") as [keyof Accounts.Account, UserUpdateParameters[keyof Accounts.Account]][])
+ .map(([x, v]) => {
+ if (!validators[x])
+ return [400, `the ${x} parameter cannot be set or is not a valid parameter`] as Message
- let allMsgs = results.map(([x,v]) => {
+ let validator =
+ (typeof validators[x] == "object"
+ ? validators[x]
+ : {
+ validator: validators[x] as Validator,
+ acceptsNull: false
+ }) as ValidatorWithSettings
+
+ if (!validator.acceptsNull && !v)
+ return [400, `the ${x} validator does not accept null values`] as Message
+
+ return [
+ x,
+ validator.validator(actor, target, body)
+ ] as [keyof Accounts.Account, Accounts.Account[keyof Accounts.Account]]
+ })
+
+ let allMsgs = results.map((v) => {
if (isMessage(v))
return v
- target[x] = v as never // lol
+ target[v[0]] = v[1] as never // lol
return [200, "OK"] as Message
})
+ await Accounts.save()
+
if (allMsgs.length == 1)
- return ctx.body(...allMsgs[0]!.reverse() as [Message[1], Message[0]]) // im sorry
+ return ctx.text(...allMsgs[0]!.reverse() as [Message[1], Message[0]]) // im sorry
else return ctx.json(allMsgs)
}
)
@@ -264,7 +324,33 @@ export default function (files: Files) {
return ctx.text("account deleted")
})
- router.get("/:user")
+ router.get("/:user", requiresAccount, async (ctx) => {
+ let acc = ctx.get("target")
+ let sessionToken = auth.tokenFor(ctx)!
+
+ return ctx.json({
+ ...acc,
+ password: undefined,
+ email:
+ auth.getType(sessionToken) == "User" ||
+ auth.getPermissions(sessionToken)?.includes("email")
+ ? acc.email
+ : undefined,
+ activeSessions: auth.AuthTokens.filter(
+ (e) =>
+ e.type != "App" &&
+ e.account == acc.id &&
+ (e.expire > Date.now() || !e.expire)
+ ).length,
+ })
+ })
+
+ router.get("/css", async (ctx) => {
+ let acc = ctx.get('account')
+ if (acc?.customCSS)
+ return ctx.redirect(`/file/${acc.customCSS}`)
+ else return ctx.text("")
+ })
return router
}
diff --git a/src/server/routes/api/v1/session.ts b/src/server/routes/api/v1/session.ts
index 3299282..a0a0c15 100644
--- a/src/server/routes/api/v1/session.ts
+++ b/src/server/routes/api/v1/session.ts
@@ -11,6 +11,7 @@ import * as Accounts from "../../../lib/accounts.js"
import * as auth from "../../../lib/auth.js"
import {
getAccount,
+ requiresAccount
} from "../../../lib/middleware.js"
import ServeError from "../../../lib/errors.js"
@@ -53,6 +54,15 @@ export default function (files: Files) {
ctx.status(200)
})
+ router.get("/", requiresAccount, ctx => {
+ let sessionToken = auth.tokenFor(ctx)
+ return ctx.json({
+ expiry: auth.AuthTokens.find(
+ (e) => e.token == sessionToken
+ )?.expire,
+ })
+ })
+
router.delete("/", (ctx) => {
if (!auth.validate(getCookie(ctx, "auth")!)) {
return ServeError(ctx, 401, "not logged in")
diff --git a/src/server/routes/api/web/api.json b/src/server/routes/api/web/api.json
new file mode 100644
index 0000000..b517aca
--- /dev/null
+++ b/src/server/routes/api/web/api.json
@@ -0,0 +1,7 @@
+{
+ "name": "web",
+ "baseURL": "/",
+ "mount": [
+ { "file": "preview", "to": "/download" }
+ ]
+}
\ No newline at end of file
diff --git a/src/server/routes/preview.ts b/src/server/routes/api/web/preview.ts
similarity index 91%
rename from src/server/routes/preview.ts
rename to src/server/routes/api/web/preview.ts
index dfdd763..909f4cf 100644
--- a/src/server/routes/preview.ts
+++ b/src/server/routes/api/web/preview.ts
@@ -1,13 +1,18 @@
import fs from "fs/promises"
import bytes from "bytes"
-import ServeError from "../lib/errors.js"
-import * as Accounts from "../lib/accounts.js"
-import type { Handler } from "hono"
-import type Files from "../lib/files.js"
-import pkg from "../../../package.json" assert {type:"json"}
+import ServeError from "../../../lib/errors.js"
+import * as Accounts from "../../../lib/accounts.js"
+import type Files from "../../../lib/files.js"
+import pkg from "../../../../../package.json" assert {type:"json"}
+import { Hono } from "hono"
+export let router = new Hono<{
+ Variables: {
+ account: Accounts.Account
+ }
+}>()
-export default (files: Files): Handler =>
- async (ctx) => {
+export default function (files: Files) {
+ router.get("/:fileId", async (ctx) => {
let acc = ctx.get("account") as Accounts.Account
const fileId = ctx.req.param("fileId")
const host = ctx.req.header("Host")
@@ -104,4 +109,7 @@ export default (files: Files): Handler =>
} else {
ServeError(ctx, 404, "file not found")
}
- }
+ })
+
+ return router
+}
\ No newline at end of file