mirror of
https://github.com/mollersuite/monofile.git
synced 2024-11-21 21:36:26 -08:00
hhhh
This commit is contained in:
parent
86f5727d83
commit
00fcf4580f
|
@ -1,6 +1,7 @@
|
|||
import { createTransport } from "nodemailer"
|
||||
import "dotenv/config"
|
||||
import config from "../../../config.json" assert {type:"json"}
|
||||
import { generateFileId } from "./files.js"
|
||||
|
||||
let mailConfig = config.mail,
|
||||
transport = createTransport({
|
||||
|
@ -33,4 +34,64 @@ export function sendMail(to: string, subject: string, content: string) {
|
|||
`<span style="font-family:monospace;padding:3px 5px 3px 5px;border-radius:8px;background-color:#1C1C1C;color:#DDDDDD;">`
|
||||
)}<br><br><span style="opacity:0.5">If you do not believe that you are the intended recipient of this email, please disregard this message.</span>`,
|
||||
})
|
||||
}
|
||||
|
||||
export namespace CodeMgr {
|
||||
|
||||
export const Intents = [
|
||||
"verifyEmail",
|
||||
"recoverAccount"
|
||||
] as const
|
||||
|
||||
export type Intent = typeof Intents[number]
|
||||
|
||||
export function isIntent(intent: string): intent is Intent { return intent in Intents }
|
||||
|
||||
export let codes = Object.fromEntries(
|
||||
Intents.map(e => [
|
||||
e,
|
||||
{byId: new Map<string, Code>(), byUser: new Map<string, Code[]>()}
|
||||
])) as Record<Intent, { byId: Map<string, Code>, byUser: Map<string, Code[]> }>
|
||||
|
||||
// this is stupid whyd i write this
|
||||
|
||||
export class Code {
|
||||
readonly id: string = generateFileId(12)
|
||||
readonly for: string
|
||||
|
||||
readonly intent: Intent
|
||||
|
||||
readonly expiryClear: NodeJS.Timeout
|
||||
|
||||
readonly data: any
|
||||
|
||||
constructor(intent: Intent, forUser: string, data?: any, time: number = 15*60*1000) {
|
||||
this.for = forUser;
|
||||
this.intent = intent
|
||||
this.expiryClear = setTimeout(this.terminate.bind(this), time)
|
||||
this.data = data
|
||||
|
||||
codes[intent].byId.set(this.id, this);
|
||||
|
||||
let byUser = codes[intent].byUser.get(this.for)
|
||||
if (!byUser) {
|
||||
byUser = []
|
||||
codes[intent].byUser.set(this.for, byUser);
|
||||
}
|
||||
|
||||
byUser.push(this)
|
||||
}
|
||||
|
||||
terminate() {
|
||||
codes[this.intent].byId.delete(this.id);
|
||||
let bu = codes[this.intent].byUser.get(this.id)!
|
||||
bu.splice(bu.indexOf(this), 1)
|
||||
clearTimeout(this.expiryClear)
|
||||
}
|
||||
|
||||
check(forUser: string) {
|
||||
return forUser === this.for
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
import * as Accounts from "./accounts.js"
|
||||
import { Handler as RequestHandler } from "hono"
|
||||
import type { Context, Handler as RequestHandler } from "hono"
|
||||
import ServeError from "../lib/errors.js"
|
||||
import * as auth from "./auth.js"
|
||||
import { setCookie } from "hono/cookie"
|
||||
|
||||
/**
|
||||
* @description Middleware which adds an account, if any, to ctx.get("account")
|
||||
|
@ -92,6 +93,15 @@ export const assertAPI = function (
|
|||
}
|
||||
}
|
||||
|
||||
// Not really middleware but a utility
|
||||
|
||||
export const login = (ctx: Context, account: string) => setCookie(ctx, "auth", auth.create(account, 3 * 24 * 60 * 60 * 1000), {
|
||||
path: "/",
|
||||
sameSite: "Strict",
|
||||
secure: true,
|
||||
httpOnly: true
|
||||
})
|
||||
|
||||
type SchemeType = "array" | "object" | "string" | "number" | "boolean"
|
||||
|
||||
interface SchemeObject {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Modules
|
||||
|
||||
|
||||
import { Hono } from "hono"
|
||||
import { type Context, Hono } from "hono"
|
||||
import { getCookie, setCookie } from "hono/cookie"
|
||||
|
||||
// Libs
|
||||
|
@ -12,12 +12,13 @@ import * as auth from "../../../lib/auth.js"
|
|||
import {
|
||||
assertAPI,
|
||||
getAccount,
|
||||
login,
|
||||
noAPIAccess,
|
||||
requiresAccount,
|
||||
requiresPermissions,
|
||||
} from "../../../lib/middleware.js"
|
||||
import ServeError from "../../../lib/errors.js"
|
||||
import { sendMail } from "../../../lib/mail.js"
|
||||
import { CodeMgr, sendMail } from "../../../lib/mail.js"
|
||||
|
||||
import Configuration from "../../../../../config.json" assert {type:"json"}
|
||||
|
||||
|
@ -29,7 +30,7 @@ const router = new Hono<{
|
|||
}>()
|
||||
|
||||
type UserUpdateParameters = Partial<Omit<Accounts.Account, "password"> & { password: string, currentPassword?: string }>
|
||||
type Message = [200 | 400 | 401 | 403 | 501, string]
|
||||
type Message = [200 | 400 | 401 | 403 | 429 | 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
|
||||
|
@ -42,12 +43,15 @@ type Validator<T extends keyof Partial<Accounts.Account>, ValueNotNull extends b
|
|||
*/
|
||||
(actor: Accounts.Account, target: Accounts.Account, params: UserUpdateParameters & (ValueNotNull extends true ? {
|
||||
[K in keyof Pick<UserUpdateParameters, T>]-? : UserUpdateParameters[K]
|
||||
} : {})) => Accounts.Account[T] | Message
|
||||
} : {}), ctx: Context) => 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
|
||||
type ValidatorWithSettings<T extends keyof Partial<Accounts.Account>> = {
|
||||
acceptsNull: true,
|
||||
validator: Validator<T, false>
|
||||
} | {
|
||||
acceptsNull?: false,
|
||||
validator: Validator<T, true>
|
||||
}
|
||||
|
||||
const validators: {
|
||||
|
@ -61,7 +65,7 @@ const validators: {
|
|||
},
|
||||
email: {
|
||||
acceptsNull: true,
|
||||
validator: (actor, target, params) => {
|
||||
validator: (actor, target, params, ctx) => {
|
||||
if (!params.currentPassword // actor on purpose here to allow admins
|
||||
|| (params.currentPassword && Accounts.password.check(actor.id, params.currentPassword)))
|
||||
return [401, "current password incorrect"]
|
||||
|
@ -76,6 +80,34 @@ const validators: {
|
|||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (typeof params.email !== "string") return [400, "email must be string"]
|
||||
if (actor.admin)
|
||||
return params.email
|
||||
|
||||
// send verification email
|
||||
|
||||
if ((CodeMgr.codes.verifyEmail.byUser.get(target.id)?.length || 0) >= 2) return [429, "you have too many active codes"]
|
||||
|
||||
let code = new CodeMgr.Code("verifyEmail", target.id, params.email)
|
||||
|
||||
sendMail(
|
||||
params.email,
|
||||
`Hey there, ${target.username} - let's connect your email`,
|
||||
`<b>Hello there!</b> You are recieving this message because you decided to link your email, <span code>${
|
||||
params.email.split("@")[0]
|
||||
}<span style="opacity:0.5">@${
|
||||
params.email.split("@")[1]
|
||||
}</span></span>, to your account, <span username>${
|
||||
target.username
|
||||
}</span>. If you would like to continue, please <a href="https://${ctx.req.header(
|
||||
"Host"
|
||||
)}/go/verify/${code.id}"><span code>click here</span></a>, or go to https://${ctx.req.header(
|
||||
"Host"
|
||||
)}/go/verify/${code.id}.`
|
||||
)
|
||||
|
||||
return [200, "please check your inbox"]
|
||||
}
|
||||
},
|
||||
password(actor, target, params) {
|
||||
|
@ -144,6 +176,7 @@ const validators: {
|
|||
}
|
||||
},
|
||||
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] ==
|
||||
|
@ -236,13 +269,8 @@ export default function (files: Files) {
|
|||
|
||||
return Accounts.create(body.username, body.password)
|
||||
.then((account) => {
|
||||
setCookie(ctx, "auth", auth.create(account, 3 * 24 * 60 * 60 * 1000), {
|
||||
path: "/",
|
||||
sameSite: "Strict",
|
||||
secure: true,
|
||||
httpOnly: true
|
||||
})
|
||||
return ctx.status(200)
|
||||
login(ctx, account)
|
||||
return ctx.text("logged in")
|
||||
})
|
||||
.catch(() => {
|
||||
return ServeError(ctx, 500, "internal server error")
|
||||
|
@ -280,7 +308,7 @@ export default function (files: Files) {
|
|||
|
||||
return [
|
||||
x,
|
||||
validator.validator(actor, target, body)
|
||||
validator.validator(actor, target, body as any, ctx)
|
||||
] as [keyof Accounts.Account, Accounts.Account[keyof Accounts.Account]]
|
||||
})
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import * as Accounts from "../../../lib/accounts.js"
|
|||
import * as auth from "../../../lib/auth.js"
|
||||
import {
|
||||
getAccount,
|
||||
login,
|
||||
requiresAccount
|
||||
} from "../../../lib/middleware.js"
|
||||
import ServeError from "../../../lib/errors.js"
|
||||
|
@ -45,13 +46,9 @@ export default function (files: Files) {
|
|||
ServeError(ctx, 400, "username or password incorrect")
|
||||
return
|
||||
}
|
||||
setCookie(ctx, "auth", auth.create(account.id, 3 * 24 * 60 * 60 * 1000), {
|
||||
path: "/",
|
||||
sameSite: "Strict",
|
||||
secure: true,
|
||||
httpOnly: true
|
||||
})
|
||||
ctx.status(200)
|
||||
|
||||
login(ctx, account.id)
|
||||
return ctx.text("logged in")
|
||||
})
|
||||
|
||||
router.get("/", requiresAccount, ctx => {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"name": "web",
|
||||
"baseURL": "/",
|
||||
"mount": [
|
||||
{ "file": "preview", "to": "/download" }
|
||||
{ "file": "preview", "to": "/download" },
|
||||
"go"
|
||||
]
|
||||
}
|
41
src/server/routes/api/web/go.ts
Normal file
41
src/server/routes/api/web/go.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import fs from "fs/promises"
|
||||
import bytes from "bytes"
|
||||
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 { CodeMgr } from "../../../lib/mail.js"
|
||||
import { Hono } from "hono"
|
||||
import { getAccount, login } from "../../../lib/middleware.js"
|
||||
export let router = new Hono<{
|
||||
Variables: {
|
||||
account: Accounts.Account
|
||||
}
|
||||
}>()
|
||||
|
||||
export default function (files: Files) {
|
||||
router.get("/verify/:code", getAccount, async (ctx) => {
|
||||
let currentAccount = ctx.get("account")
|
||||
let code = CodeMgr.codes.verifyEmail.byId.get(ctx.req.param("code"))
|
||||
|
||||
if (code) {
|
||||
if (currentAccount != undefined && !code.check(currentAccount.id)) {
|
||||
return ServeError(ctx, 403, "you are logged in on a different account")
|
||||
}
|
||||
|
||||
if (!currentAccount) {
|
||||
login(ctx, code.for)
|
||||
let ac = Accounts.getFromId(code.for)
|
||||
if (ac) currentAccount = ac
|
||||
else return ServeError(ctx, 401, "could not locate account")
|
||||
}
|
||||
|
||||
currentAccount.email = code.data
|
||||
await Accounts.save()
|
||||
|
||||
return ctx.redirect('/')
|
||||
} else return ServeError(ctx, 404, "code not found")
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
|
@ -5,6 +5,7 @@ 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"
|
||||
import { getAccount } from "../../../lib/middleware.js"
|
||||
export let router = new Hono<{
|
||||
Variables: {
|
||||
account: Accounts.Account
|
||||
|
@ -12,7 +13,7 @@ export let router = new Hono<{
|
|||
}>()
|
||||
|
||||
export default function (files: Files) {
|
||||
router.get("/:fileId", async (ctx) => {
|
||||
router.get("/:fileId", getAccount, async (ctx) => {
|
||||
let acc = ctx.get("account") as Accounts.Account
|
||||
const fileId = ctx.req.param("fileId")
|
||||
const host = ctx.req.header("Host")
|
||||
|
|
Loading…
Reference in a new issue