Okay that's enough for now

This commit is contained in:
May 2024-03-08 20:02:27 -08:00
parent e624e19802
commit 86f5727d83
12 changed files with 173 additions and 71 deletions

View file

@ -19,7 +19,7 @@
<link
rel="stylesheet"
href="/api/v1/account/customization/css"
href="/api/v1/account/me/css"
>
<link

View file

@ -17,7 +17,7 @@
<link
rel="stylesheet"
href="/api/v1/account/customization/css"
href="/api/v1/account/me/css"
>
<meta

View file

@ -15,7 +15,7 @@
href="/static/assets/icons/icon_temp.svg"
/>
<link rel="stylesheet" href="/api/v1/account/customization/css" />
<link rel="stylesheet" href="/api/v1/account/me/css" />
<meta
name="viewport"

View file

@ -6,7 +6,7 @@ import { readFile } from "fs/promises"
import Files from "./lib/files.js"
import { getAccount } from "./lib/middleware.js"
import APIRouter from "./routes/api.js"
import preview from "./routes/preview.js"
import preview from "./routes/api/web/preview.js"
import {fileURLToPath} from "url"
import {dirname} from "path"
import pkg from "../../package.json" assert {type:"json"}
@ -95,10 +95,6 @@ app.get("/", async (ctx) =>
)
)
// serve download page
app.get("/download/:fileId", getAccount, preview(files))
/*
routes should be in this order:

View file

@ -11,8 +11,6 @@ let mailConfig = config.mail,
},
})
// lazy but
/**
* @description Sends an email
* @param to Target email address

View file

@ -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(

View file

@ -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) {
`<b>Hello there!</b> This email is to notify you of a password change that an administrator, <span username>${acc.username}</span>, 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]) {

View file

@ -105,7 +105,6 @@ export default function (files: Files) {
}
}
)
// upload handlers
primaryApi.post(
"/upload",

View file

@ -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<UserUpdateParameters, T>`
// @Jack5079 make typings better if possible
const validators: {
[T in keyof Partial<Accounts.Account>]:
type Validator<T extends keyof Partial<Accounts.Account>, 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 & {
(actor: Accounts.Account, target: Accounts.Account, params: UserUpdateParameters & (ValueNotNull extends true ? {
[K in keyof Pick<UserUpdateParameters, T>]-? : UserUpdateParameters[K]
}) => Accounts.Account[T] | Message
} : {})) => Accounts.Account[T] | Message
// this type is so stupid stg
interface ValidatorWithSettings<T extends keyof Partial<Accounts.Account>> {
acceptsNull?: boolean,
validator: Validator<T, this["acceptsNull"] extends true ? true : false> // i give upp ill fix this later
}
const validators: {
[T in keyof Partial<Accounts.Account>]:
Validator<T, true> | ValidatorWithSettings<T>
} = {
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`,
`<b>Hello there!</b> Your email address (<span code>${target.email}</span>) has been disconnected from the monofile account <span username>${target.username}</span>. 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) {
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<typeof x, false>,
acceptsNull: false
}) as ValidatorWithSettings<typeof x>
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
}

View file

@ -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")

View file

@ -0,0 +1,7 @@
{
"name": "web",
"baseURL": "/",
"mount": [
{ "file": "preview", "to": "/download" }
]
}

View file

@ -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
}