mirror of
https://github.com/mollersuite/monofile.git
synced 2024-11-24 06:36:27 -08:00
i think we're done actually
This commit is contained in:
parent
8a26ace11f
commit
f0a2450082
|
@ -9,7 +9,7 @@ import { fileURLToPath } from "url"
|
||||||
import { dirname } from "path"
|
import { dirname } from "path"
|
||||||
import config from "./lib/config.js"
|
import config from "./lib/config.js"
|
||||||
|
|
||||||
const app = new Hono()
|
const app = new Hono({strict: false})
|
||||||
|
|
||||||
app.get(
|
app.get(
|
||||||
"/static/assets/*",
|
"/static/assets/*",
|
||||||
|
@ -78,7 +78,7 @@ apiRouter.loadAPIMethods().then(() => {
|
||||||
app.fetch(
|
app.fetch(
|
||||||
new Request(
|
new Request(
|
||||||
new URL(
|
new URL(
|
||||||
"/api/v1/info",
|
"/api/v1",
|
||||||
ctx.req.raw.url
|
ctx.req.raw.url
|
||||||
).href,
|
).href,
|
||||||
ctx.req.raw
|
ctx.req.raw
|
||||||
|
|
|
@ -10,6 +10,7 @@ import "dotenv/config"
|
||||||
import * as Accounts from "./accounts.js"
|
import * as Accounts from "./accounts.js"
|
||||||
import { z } from "zod"
|
import { z } from "zod"
|
||||||
import * as schemas from "./schemas/files.js"
|
import * as schemas from "./schemas/files.js"
|
||||||
|
import { issuesToMessage } from "./middleware.js"
|
||||||
|
|
||||||
export let alphanum = Array.from(
|
export let alphanum = Array.from(
|
||||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"
|
||||||
|
@ -504,7 +505,7 @@ export class UploadStream extends Writable {
|
||||||
let check = schemas.FileId.safeParse(id);
|
let check = schemas.FileId.safeParse(id);
|
||||||
|
|
||||||
if (!check.success)
|
if (!check.success)
|
||||||
return this.destroy(new WebError(400, check.error.message))
|
return this.destroy(new WebError(400, issuesToMessage(check.error.issues)))
|
||||||
|
|
||||||
if (this.files.files[id] && this.files.files[id].owner != this.owner)
|
if (this.files.files[id] && this.files.files[id].owner != this.owner)
|
||||||
return this.destroy(new WebError(403, "you don't own this file"))
|
return this.destroy(new WebError(403, "you don't own this file"))
|
||||||
|
|
|
@ -3,7 +3,7 @@ import type { Context, Handler as RequestHandler } from "hono"
|
||||||
import ServeError from "../lib/errors.js"
|
import ServeError from "../lib/errors.js"
|
||||||
import * as auth from "./auth.js"
|
import * as auth from "./auth.js"
|
||||||
import { setCookie } from "hono/cookie"
|
import { setCookie } from "hono/cookie"
|
||||||
import { ZodObject } from "zod"
|
import { z } from "zod"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @description Middleware which adds an account, if any, to ctx.get("account")
|
* @description Middleware which adds an account, if any, to ctx.get("account")
|
||||||
|
@ -38,7 +38,6 @@ export const requiresAdmin: RequestHandler = function (ctx, next) {
|
||||||
* @param tokenPermissions Permissions which your route requires.
|
* @param tokenPermissions Permissions which your route requires.
|
||||||
* @returns Express middleware
|
* @returns Express middleware
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const requiresPermissions = function (
|
export const requiresPermissions = function (
|
||||||
...tokenPermissions: auth.TokenPermission[]
|
...tokenPermissions: auth.TokenPermission[]
|
||||||
): RequestHandler {
|
): RequestHandler {
|
||||||
|
@ -94,6 +93,18 @@ export const assertAPI = function (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const issuesToMessage = function(issues: z.ZodIssue[]) {
|
||||||
|
return issues.map(e => `${e.path}: ${e.code} :: ${e.message}`).join("; ")
|
||||||
|
}
|
||||||
|
|
||||||
|
export const scheme = function(scheme: z.ZodTypeAny): RequestHandler {
|
||||||
|
return async function(ctx, next) {
|
||||||
|
let chk = scheme.safeParse(await ctx.req.json())
|
||||||
|
if (chk.success) return next()
|
||||||
|
else return ServeError(ctx, 400, issuesToMessage(chk.error.issues))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Not really middleware but a utility
|
// Not really middleware but a utility
|
||||||
|
|
||||||
export const login = (ctx: Context, account: string) => setCookie(ctx, "auth", auth.create(account, 3 * 24 * 60 * 60 * 1000), {
|
export const login = (ctx: Context, account: string) => setCookie(ctx, "auth", auth.create(account, 3 * 24 * 60 * 60 * 1000), {
|
||||||
|
@ -102,11 +113,3 @@ export const login = (ctx: Context, account: string) => setCookie(ctx, "auth", a
|
||||||
secure: true,
|
secure: true,
|
||||||
httpOnly: true
|
httpOnly: true
|
||||||
})
|
})
|
||||||
|
|
||||||
export const scheme = function(scheme: ZodObject<any>): RequestHandler {
|
|
||||||
return function(ctx, next) {
|
|
||||||
let chk = scheme.safeParse(ctx.req.json())
|
|
||||||
if (chk.success) next()
|
|
||||||
else ServeError(ctx, 400, chk.error.message)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,6 +11,7 @@ import * as auth from "../../../lib/auth.js"
|
||||||
import {
|
import {
|
||||||
assertAPI,
|
assertAPI,
|
||||||
getAccount,
|
getAccount,
|
||||||
|
issuesToMessage,
|
||||||
login,
|
login,
|
||||||
noAPIAccess,
|
noAPIAccess,
|
||||||
requiresAccount,
|
requiresAccount,
|
||||||
|
@ -43,8 +44,7 @@ type Message = [200 | 400 | 401 | 403 | 429 | 501, string]
|
||||||
// @Jack5079 make typings better if possible
|
// @Jack5079 make typings better if possible
|
||||||
|
|
||||||
type Validator<
|
type Validator<
|
||||||
T extends keyof Partial<Accounts.Account>,
|
T extends keyof Partial<Accounts.Account>
|
||||||
ValueNotNull extends boolean,
|
|
||||||
> =
|
> =
|
||||||
/**
|
/**
|
||||||
* @param actor The account performing this action
|
* @param actor The account performing this action
|
||||||
|
@ -55,38 +55,33 @@ type Validator<
|
||||||
actor: Accounts.Account,
|
actor: Accounts.Account,
|
||||||
target: Accounts.Account,
|
target: Accounts.Account,
|
||||||
params: UserUpdateParameters &
|
params: UserUpdateParameters &
|
||||||
(ValueNotNull extends true
|
{
|
||||||
? {
|
|
||||||
[K in keyof Pick<
|
[K in keyof Pick<
|
||||||
UserUpdateParameters,
|
UserUpdateParameters,
|
||||||
T
|
T
|
||||||
>]-?: UserUpdateParameters[K]
|
>]-?: UserUpdateParameters[K]
|
||||||
}
|
},
|
||||||
: {}),
|
|
||||||
ctx: Context
|
ctx: Context
|
||||||
) => Accounts.Account[T] | Message
|
) => Accounts.Account[T] | Message
|
||||||
|
|
||||||
// this type is so stupid stg
|
type SchemedValidator<
|
||||||
type ValidatorWithSettings<T extends keyof Partial<Accounts.Account>> =
|
T extends keyof Partial<Accounts.Account>
|
||||||
| {
|
> = {
|
||||||
acceptsNull: true
|
validator: Validator<T>,
|
||||||
validator: Validator<T, false>
|
schema: z.ZodTypeAny
|
||||||
}
|
|
||||||
| {
|
|
||||||
acceptsNull?: false
|
|
||||||
validator: Validator<T, true>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const validators: {
|
const validators: {
|
||||||
[T in keyof Partial<Accounts.Account>]:
|
[T in keyof Partial<Accounts.Account>]: SchemedValidator<T>
|
||||||
| Validator<T, true>
|
|
||||||
| ValidatorWithSettings<T>
|
|
||||||
} = {
|
} = {
|
||||||
defaultFileVisibility(actor, target, params) {
|
defaultFileVisibility: {
|
||||||
|
schema: FileSchemas.FileVisibility,
|
||||||
|
validator: (actor, target, params) => {
|
||||||
return params.defaultFileVisibility
|
return params.defaultFileVisibility
|
||||||
|
}
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
acceptsNull: true,
|
schema: AccountSchemas.Account.shape.email.optional(),
|
||||||
validator: (actor, target, params, ctx) => {
|
validator: (actor, target, params, ctx) => {
|
||||||
if (
|
if (
|
||||||
!params.currentPassword || // actor on purpose here to allow admins
|
!params.currentPassword || // actor on purpose here to allow admins
|
||||||
|
@ -139,7 +134,9 @@ const validators: {
|
||||||
return [200, "please check your inbox"]
|
return [200, "please check your inbox"]
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
password(actor, target, params) {
|
password: {
|
||||||
|
schema: AccountSchemas.StringPassword,
|
||||||
|
validator: (actor, target, params) => {
|
||||||
if (
|
if (
|
||||||
!params.currentPassword || // actor on purpose here to allow admins
|
!params.currentPassword || // actor on purpose here to allow admins
|
||||||
(params.currentPassword &&
|
(params.currentPassword &&
|
||||||
|
@ -158,8 +155,11 @@ const validators: {
|
||||||
}
|
}
|
||||||
|
|
||||||
return Accounts.password.hash(params.password)
|
return Accounts.password.hash(params.password)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
username(actor, target, params) {
|
username: {
|
||||||
|
schema: AccountSchemas.Username,
|
||||||
|
validator: (actor, target, params) => {
|
||||||
if (
|
if (
|
||||||
!params.currentPassword || // actor on purpose here to allow admins
|
!params.currentPassword || // actor on purpose here to allow admins
|
||||||
(params.currentPassword &&
|
(params.currentPassword &&
|
||||||
|
@ -181,40 +181,15 @@ const validators: {
|
||||||
}
|
}
|
||||||
|
|
||||||
return params.username
|
return params.username
|
||||||
|
}
|
||||||
},
|
},
|
||||||
customCSS: {
|
admin: {
|
||||||
acceptsNull: true,
|
schema: z.boolean(),
|
||||||
validator: (actor, target, params) => {
|
validator: (actor, target, params) => {
|
||||||
if (FileSchemas.FileId.safeParse(params.customCSS).success)
|
|
||||||
return params.customCSS
|
|
||||||
else return [400, "bad file id"]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
embed(actor, target, params) {
|
|
||||||
if (typeof params.embed !== "object")
|
|
||||||
return [400, "must use an object for embed"]
|
|
||||||
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
|
if (actor.admin && !target.admin) return params.admin
|
||||||
else if (!actor.admin) return [400, "cannot promote yourself"]
|
else if (!actor.admin) return [400, "cannot promote yourself"]
|
||||||
else return [400, "cannot demote an admin"]
|
else return [400, "cannot demote an admin"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,23 +281,11 @@ export default function (files: Files) {
|
||||||
`the ${x} parameter cannot be set or is not a valid parameter`,
|
`the ${x} parameter cannot be set or is not a valid parameter`,
|
||||||
] as Message
|
] as Message
|
||||||
|
|
||||||
let validator = (
|
let validator = validators[x]!
|
||||||
typeof validators[x] == "object"
|
|
||||||
? validators[x]
|
|
||||||
: {
|
|
||||||
validator: validators[x] as Validator<
|
|
||||||
typeof x,
|
|
||||||
false
|
|
||||||
>,
|
|
||||||
acceptsNull: false,
|
|
||||||
}
|
|
||||||
) as ValidatorWithSettings<typeof x>
|
|
||||||
|
|
||||||
if (!validator.acceptsNull && !v)
|
let check = validator.schema.safeParse(v)
|
||||||
return [
|
if (!check.success)
|
||||||
400,
|
return [400, issuesToMessage(check.error.issues)]
|
||||||
`the ${x} validator does not accept null values`,
|
|
||||||
] as Message
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
x,
|
x,
|
||||||
|
@ -391,7 +354,7 @@ export default function (files: Files) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get("/css", async (ctx) => {
|
router.get("/:user/css", async (ctx) => {
|
||||||
let acc = ctx.get("account")
|
let acc = ctx.get("account")
|
||||||
if (acc?.customCSS) return ctx.redirect(`/file/${acc.customCSS}`)
|
if (acc?.customCSS) return ctx.redirect(`/file/${acc.customCSS}`)
|
||||||
else return ctx.text("")
|
else return ctx.text("")
|
||||||
|
|
|
@ -4,7 +4,10 @@
|
||||||
"mount": [
|
"mount": [
|
||||||
"account",
|
"account",
|
||||||
"session",
|
"session",
|
||||||
"info",
|
{
|
||||||
|
"file": "index",
|
||||||
|
"to": "/"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file": "file/index",
|
"file": "file/index",
|
||||||
"to": "/file"
|
"to": "/file"
|
||||||
|
|
|
@ -12,9 +12,12 @@ import * as auth from "../../../lib/auth.js"
|
||||||
import {
|
import {
|
||||||
getAccount,
|
getAccount,
|
||||||
login,
|
login,
|
||||||
requiresAccount
|
requiresAccount,
|
||||||
|
scheme
|
||||||
} from "../../../lib/middleware.js"
|
} from "../../../lib/middleware.js"
|
||||||
import ServeError from "../../../lib/errors.js"
|
import ServeError from "../../../lib/errors.js"
|
||||||
|
import { AccountSchemas } from "../../../lib/schemas/index.js"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
const router = new Hono<{
|
const router = new Hono<{
|
||||||
Variables: {
|
Variables: {
|
||||||
|
@ -25,15 +28,11 @@ const router = new Hono<{
|
||||||
router.use(getAccount)
|
router.use(getAccount)
|
||||||
|
|
||||||
export default function (files: Files) {
|
export default function (files: Files) {
|
||||||
router.post("/", async (ctx, res) => {
|
router.post("/",scheme(z.object({
|
||||||
|
username: AccountSchemas.Username,
|
||||||
|
password: AccountSchemas.StringPassword
|
||||||
|
})), async (ctx) => {
|
||||||
const body = await ctx.req.json()
|
const body = await ctx.req.json()
|
||||||
if (
|
|
||||||
typeof body.username != "string" ||
|
|
||||||
typeof body.password != "string"
|
|
||||||
) {
|
|
||||||
ServeError(ctx, 400, "please provide a username or password")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auth.validate(getCookie(ctx, "auth")!)) {
|
if (auth.validate(getCookie(ctx, "auth")!)) {
|
||||||
ServeError(ctx, 400, "you are already logged in")
|
ServeError(ctx, 400, "you are already logged in")
|
||||||
|
|
Loading…
Reference in a new issue