mirror of
https://github.com/mollersuite/monofile.git
synced 2024-11-24 06:36:27 -08:00
This builds but probably doesn't work yet
This commit is contained in:
parent
c85de0e86a
commit
594efc49e0
|
@ -19,3 +19,5 @@ MAIL__SECURE=
|
||||||
MAIL__SEND_FROM=
|
MAIL__SEND_FROM=
|
||||||
MAIL__USER=
|
MAIL__USER=
|
||||||
MAIL__PASS=
|
MAIL__PASS=
|
||||||
|
|
||||||
|
JWT_SECRET=
|
|
@ -7,27 +7,35 @@ import { AuthSchemas } from "./schemas/index.js"
|
||||||
import DbFile from "./dbfile.js"
|
import DbFile from "./dbfile.js"
|
||||||
import * as jose from "jose"
|
import * as jose from "jose"
|
||||||
import { AccountResolvable } from "./accounts.js"
|
import { AccountResolvable } from "./accounts.js"
|
||||||
|
import config from "./config.js"
|
||||||
export let AuthTokenTO: { [key: string]: NodeJS.Timeout } = {}
|
export let AuthTokenTO: { [key: string]: NodeJS.Timeout } = {}
|
||||||
|
|
||||||
export type TokenPermission = z.infer<typeof AuthSchemas.Scope>
|
export type Scope = z.infer<typeof AuthSchemas.Scope>
|
||||||
export type TokenType = z.infer<typeof AuthSchemas.TokenType>
|
export type TokenType = z.infer<typeof AuthSchemas.TokenType>
|
||||||
export type AuthToken = z.infer<typeof AuthSchemas.AuthToken>
|
export type AuthToken = z.infer<typeof AuthSchemas.AuthToken>
|
||||||
|
export type TokenResolvable = string | AuthToken
|
||||||
|
|
||||||
export const Db = new DbFile<AuthToken[]>("tokens", [])
|
export const Db = new DbFile<AuthToken[]>("tokens", [])
|
||||||
|
|
||||||
|
export function resolve(token: TokenResolvable) {
|
||||||
|
let resolved = typeof token == "object" ? token : Db.data.find(e => e.id == token)
|
||||||
|
if (resolved && (resolved.expire == null || Date.now() < resolved.expire))
|
||||||
|
return resolved
|
||||||
|
}
|
||||||
|
|
||||||
export function create(
|
export function create(
|
||||||
account: AccountResolvable,
|
account: AccountResolvable,
|
||||||
expire: number | null = 24 * 60 * 60 * 1000,
|
expire: number | null = 24 * 60 * 60 * 1000,
|
||||||
type: TokenType = "User",
|
type: TokenType = "User",
|
||||||
tokenPermissions?: TokenPermission[]
|
scopes?: Scope[]
|
||||||
) {
|
) {
|
||||||
let token = AuthSchemas.AuthToken.parse({
|
let token = AuthSchemas.AuthToken.parse({
|
||||||
account,
|
account,
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
expire: typeof expire == "number" ? Date.now() + expire : null,
|
expire: typeof expire == "number" ? Date.now() + expire : null,
|
||||||
type,
|
type,
|
||||||
tokenPermissions:
|
scopes:
|
||||||
type != "User" ? tokenPermissions || ["user"] : undefined,
|
type != "User" ? scopes || ["user"] : undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
Db.data.push(token)
|
Db.data.push(token)
|
||||||
|
@ -35,59 +43,75 @@ export function create(
|
||||||
|
|
||||||
Db.save()
|
Db.save()
|
||||||
|
|
||||||
return token.token
|
return token
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tokenFor(ctx: Context) {
|
|
||||||
return (
|
export async function getJwtId(jwt: string) {
|
||||||
getCookie(ctx, "auth") ||
|
let result = await jose.jwtVerify(jwt, config.jwtSecret).catch(e => null)
|
||||||
(ctx.req.header("authorization")?.startsWith("Bearer ")
|
return result ? result.payload.jti : undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeJwt(_token: TokenResolvable) {
|
||||||
|
let token = resolve(_token)!
|
||||||
|
let jwt = new jose.SignJWT({
|
||||||
|
exp: token.expire || undefined,
|
||||||
|
sub: token.account,
|
||||||
|
jti: token.id,
|
||||||
|
...(token.type != "User" ? { scope: token.scopes } : {})
|
||||||
|
})
|
||||||
|
|
||||||
|
return jwt.sign(config.jwtSecret)
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function tokenFor(ctx: Context) {
|
||||||
|
let token =
|
||||||
|
getCookie(ctx, "auth")
|
||||||
|
|| (ctx.req.header("authorization")?.startsWith("Bearer ")
|
||||||
? ctx.req.header("authorization")?.split(" ")[1]
|
? ctx.req.header("authorization")?.split(" ")[1]
|
||||||
: undefined)
|
: undefined)
|
||||||
)
|
if (!token) return
|
||||||
|
|
||||||
|
let jti = await getJwtId(token)
|
||||||
|
return jti
|
||||||
}
|
}
|
||||||
|
|
||||||
function getToken(token: string) {
|
export function validate(token: TokenResolvable) {
|
||||||
return Db.data.find(
|
return resolve(token)?.account
|
||||||
(e) => e.token == token && (e.expire == null || Date.now() < e.expire)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function validate(token: string) {
|
export function getType(token: TokenResolvable) {
|
||||||
return getToken(token)?.account
|
return resolve(token)?.type
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getType(token: string): TokenType | undefined {
|
export function getScopes(token: TokenResolvable): Scope[] | undefined {
|
||||||
return getToken(token)?.type
|
let tok = resolve(token)
|
||||||
}
|
if (tok && "scopes" in tok)
|
||||||
|
return tok.scopes
|
||||||
export function getPermissions(token: string): TokenPermission[] | undefined {
|
|
||||||
let tok = getToken(token)
|
|
||||||
if (tok && "tokenPermissions" in tok)
|
|
||||||
return tok.tokenPermissions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tokenTimer(token: AuthToken) {
|
export function tokenTimer(token: AuthToken) {
|
||||||
if (!token.expire) return
|
if (!token.expire) return
|
||||||
|
|
||||||
if (Date.now() >= token.expire) {
|
if (Date.now() >= token.expire) {
|
||||||
invalidate(token.token)
|
invalidate(token)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthTokenTO[token.token] = setTimeout(
|
AuthTokenTO[token.id] = setTimeout(
|
||||||
() => invalidate(token.token),
|
() => invalidate(token),
|
||||||
token.expire - Date.now()
|
token.expire - Date.now()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function invalidate(token: string) {
|
export function invalidate(_token: TokenResolvable) {
|
||||||
if (AuthTokenTO[token]) {
|
let token = resolve(_token)!
|
||||||
clearTimeout(AuthTokenTO[token])
|
if (AuthTokenTO[token.id]) {
|
||||||
|
clearTimeout(AuthTokenTO[token.id])
|
||||||
}
|
}
|
||||||
|
|
||||||
Db.data.splice(
|
Db.data.splice(
|
||||||
Db.data.findIndex((e) => e.token == token),
|
Db.data.indexOf(token),
|
||||||
1
|
1
|
||||||
)
|
)
|
||||||
Db.save()
|
Db.save()
|
||||||
|
|
|
@ -26,7 +26,9 @@ export interface Configuration {
|
||||||
}
|
}
|
||||||
user: string
|
user: string
|
||||||
pass: string
|
pass: string
|
||||||
}
|
},
|
||||||
|
|
||||||
|
jwtSecret: Buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ClientConfiguration {
|
export interface ClientConfiguration {
|
||||||
|
@ -72,4 +74,6 @@ export default {
|
||||||
user: process.env.MAIL__USER,
|
user: process.env.MAIL__USER,
|
||||||
pass: process.env.MAIL__PASS,
|
pass: process.env.MAIL__PASS,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
jwtSecret: Buffer.from(process.env.JWT_SECRET!)
|
||||||
} as Configuration
|
} as Configuration
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as Accounts from "./accounts.js"
|
import * as Accounts from "./accounts.js"
|
||||||
import type { Context, Handler as RequestHandler } from "hono"
|
import type { Context, Hono, 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"
|
||||||
|
@ -9,8 +9,8 @@ import { codes } from "./codes.js"
|
||||||
/**
|
/**
|
||||||
* @description Middleware which adds an account, if any, to ctx.get("account")
|
* @description Middleware which adds an account, if any, to ctx.get("account")
|
||||||
*/
|
*/
|
||||||
export const getAccount: RequestHandler = function (ctx, next) {
|
export const getAccount: RequestHandler = async function (ctx, next) {
|
||||||
let account = Accounts.getFromToken(auth.tokenFor(ctx)!)
|
let account = Accounts.getFromToken((await auth.tokenFor(ctx))!)
|
||||||
if (account?.suspension)
|
if (account?.suspension)
|
||||||
setCookie(ctx, "auth", "")
|
setCookie(ctx, "auth", "")
|
||||||
ctx.set("account", account)
|
ctx.set("account", account)
|
||||||
|
@ -22,10 +22,10 @@ export const getAccount: RequestHandler = function (ctx, next) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const getTarget: RequestHandler = async (ctx, next) => {
|
export const getTarget: RequestHandler = async (ctx, next) => {
|
||||||
let tok = auth.tokenFor(ctx)
|
let tok = await auth.tokenFor(ctx)
|
||||||
let permissions
|
let permissions
|
||||||
if (tok && auth.getType(tok) != "User")
|
if (tok && auth.getType(tok) != "User")
|
||||||
permissions = auth.getPermissions(tok)
|
permissions = auth.getScopes(tok)
|
||||||
|
|
||||||
let actor = ctx.get("account")
|
let actor = ctx.get("account")
|
||||||
|
|
||||||
|
@ -59,10 +59,10 @@ export const getTarget: RequestHandler = async (ctx, next) => {
|
||||||
* @description Blocks routes with a target user set to the account performing the action from bot tokens which do not have the manage_account permission
|
* @description Blocks routes with a target user set to the account performing the action from bot tokens which do not have the manage_account permission
|
||||||
*/
|
*/
|
||||||
export const accountMgmtRoute: RequestHandler = async (ctx,next) => {
|
export const accountMgmtRoute: RequestHandler = async (ctx,next) => {
|
||||||
let tok = auth.tokenFor(ctx)
|
let tok = await auth.tokenFor(ctx)
|
||||||
let permissions
|
let permissions
|
||||||
if (tok && auth.getType(tok) != "User")
|
if (tok && auth.getType(tok) != "User")
|
||||||
permissions = auth.getPermissions(tok)
|
permissions = auth.getScopes(tok)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(
|
(
|
||||||
|
@ -110,26 +110,27 @@ 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 requiresScopes = function (
|
||||||
...tokenPermissions: auth.TokenPermission[]
|
...wantsScopes: auth.Scope[]
|
||||||
): RequestHandler {
|
): RequestHandler {
|
||||||
return function (ctx, next) {
|
return async function (ctx, next) {
|
||||||
let token = auth.tokenFor(ctx)!
|
let token = (await auth.tokenFor(ctx))!
|
||||||
let type = auth.getType(token)
|
let type = auth.getType(token)
|
||||||
|
|
||||||
if (type != "User") {
|
if (type != "User") {
|
||||||
let permissions = auth.getPermissions(token)
|
let scopes = auth.getScopes(token)
|
||||||
|
|
||||||
if (!permissions) return ServeError(ctx, 403, "insufficient permissions")
|
if (!scopes) return ServeError(ctx, 403, "insufficient permissions")
|
||||||
else {
|
else {
|
||||||
for (let v of tokenPermissions) {
|
for (let v of wantsScopes) {
|
||||||
if (!permissions.includes(v as auth.TokenPermission)) {
|
if (!scopes.includes(v)) {
|
||||||
return ServeError(ctx, 403, "insufficient permissions")
|
return ServeError(ctx, 403, "insufficient permissions")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return next()
|
|
||||||
}
|
}
|
||||||
} else return next()
|
}
|
||||||
|
|
||||||
|
return next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,8 +138,8 @@ export const requiresPermissions = function (
|
||||||
* @description Blocks requests based on whether or not the token being used to access the route is of type `User`.
|
* @description Blocks requests based on whether or not the token being used to access the route is of type `User`.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export const noAPIAccess: RequestHandler = function (ctx, next) {
|
export const noAPIAccess: RequestHandler = async function (ctx, next) {
|
||||||
if (auth.getType(auth.tokenFor(ctx)!) == "App")
|
if (auth.getType((await auth.tokenFor(ctx))!) == "App")
|
||||||
return ServeError(ctx, 403, "apps are not allowed to access this endpoint")
|
return ServeError(ctx, 403, "apps are not allowed to access this endpoint")
|
||||||
else return next()
|
else return next()
|
||||||
}
|
}
|
||||||
|
@ -150,8 +151,8 @@ export const noAPIAccess: RequestHandler = function (ctx, next) {
|
||||||
export const assertAPI = function (
|
export const assertAPI = function (
|
||||||
condition: (ctx: Context) => boolean
|
condition: (ctx: Context) => boolean
|
||||||
): RequestHandler {
|
): RequestHandler {
|
||||||
return function (ctx, next) {
|
return async function (ctx, next) {
|
||||||
let reqToken = auth.tokenFor(ctx)!
|
let reqToken = (await auth.tokenFor(ctx))!
|
||||||
if (
|
if (
|
||||||
auth.getType(reqToken) != "User" &&
|
auth.getType(reqToken) != "User" &&
|
||||||
condition(ctx)
|
condition(ctx)
|
||||||
|
@ -184,9 +185,9 @@ export function scheme(scheme: z.ZodTypeAny, transformer: (ctx: Context) => Prom
|
||||||
|
|
||||||
// Not really middleware but a utility
|
// Not really middleware but a utility
|
||||||
|
|
||||||
export const login = (ctx: Context, account: string) => {
|
export const login = async (ctx: Context, account: string) => {
|
||||||
let token = auth.create(account, 3 * 24 * 60 * 60 * 1000)
|
let token = auth.create(account, 3 * 24 * 60 * 60 * 1000)
|
||||||
setCookie(ctx, "auth", token, {
|
setCookie(ctx, "auth", await auth.makeJwt(token), {
|
||||||
path: "/",
|
path: "/",
|
||||||
sameSite: "Strict",
|
sameSite: "Strict",
|
||||||
secure: true,
|
secure: true,
|
||||||
|
@ -206,3 +207,14 @@ export const verifyPoi = (user: string, poi?: string, wantsMfaPoi: boolean = fal
|
||||||
poiCode.terminate()
|
poiCode.terminate()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const mirror = (apiRoot: Hono, ctx: Context, url: string, init: Partial<RequestInit>) => apiRoot.fetch(
|
||||||
|
new Request(
|
||||||
|
(new URL(url, ctx.req.raw.url)).href,
|
||||||
|
{
|
||||||
|
...ctx.req.raw,
|
||||||
|
...init
|
||||||
|
}
|
||||||
|
),
|
||||||
|
ctx.env
|
||||||
|
)
|
|
@ -17,7 +17,7 @@ export const TokenType = z.enum([
|
||||||
|
|
||||||
const BaseAuthToken = z.object({
|
const BaseAuthToken = z.object({
|
||||||
account: z.string(),
|
account: z.string(),
|
||||||
token: z.string(),
|
id: z.string(),
|
||||||
expire: z.number()
|
expire: z.number()
|
||||||
.nullable()
|
.nullable()
|
||||||
.refine(e => e == null || e > Date.now(), "expiration must be after now"),
|
.refine(e => e == null || e > Date.now(), "expiration must be after now"),
|
||||||
|
@ -31,10 +31,10 @@ export const AuthToken = z.discriminatedUnion("type",[
|
||||||
}),
|
}),
|
||||||
BaseAuthToken.extend({
|
BaseAuthToken.extend({
|
||||||
type: z.literal("ApiKey"),
|
type: z.literal("ApiKey"),
|
||||||
tokenPermissions: z.array(Scope).default(["user"])
|
scopes: z.array(Scope).default(["user"])
|
||||||
}),
|
}),
|
||||||
BaseAuthToken.extend({
|
BaseAuthToken.extend({
|
||||||
type: z.literal("App"),
|
type: z.literal("App"),
|
||||||
tokenPermissions: z.array(Scope).default(["user"])
|
scopes: z.array(Scope).default(["user"])
|
||||||
})
|
})
|
||||||
])
|
])
|
|
@ -7,7 +7,7 @@ import {
|
||||||
getAccount,
|
getAccount,
|
||||||
requiresAccount,
|
requiresAccount,
|
||||||
requiresAdmin,
|
requiresAdmin,
|
||||||
requiresPermissions,
|
requiresScopes,
|
||||||
} from "../../../lib/middleware.js"
|
} from "../../../lib/middleware.js"
|
||||||
import Files from "../../../lib/files.js"
|
import Files from "../../../lib/files.js"
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ adminRoutes
|
||||||
.use(getAccount)
|
.use(getAccount)
|
||||||
.use(requiresAccount)
|
.use(requiresAccount)
|
||||||
.use(requiresAdmin)
|
.use(requiresAdmin)
|
||||||
.use(requiresPermissions("manage_server"))
|
.use(requiresScopes("manage_server"))
|
||||||
|
|
||||||
export default function (files: Files) {
|
export default function (files: Files) {
|
||||||
adminRoutes.post("/reset", async (ctx) => {
|
adminRoutes.post("/reset", async (ctx) => {
|
||||||
|
@ -42,7 +42,7 @@ export default function (files: Files) {
|
||||||
Accounts.password.set(targetAccount.id, body.password)
|
Accounts.password.set(targetAccount.id, body.password)
|
||||||
auth.Db.data.filter((e) => e.account == targetAccount?.id).forEach(
|
auth.Db.data.filter((e) => e.account == targetAccount?.id).forEach(
|
||||||
(v) => {
|
(v) => {
|
||||||
auth.invalidate(v.token)
|
auth.invalidate(v.id)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -107,7 +107,7 @@ export default function (files: Files) {
|
||||||
let accId = targetAccount.id
|
let accId = targetAccount.id
|
||||||
|
|
||||||
auth.Db.data.filter((e) => e.account == accId).forEach((v) => {
|
auth.Db.data.filter((e) => e.account == accId).forEach((v) => {
|
||||||
auth.invalidate(v.token)
|
auth.invalidate(v.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
let cpl = () =>
|
let cpl = () =>
|
||||||
|
|
|
@ -5,9 +5,10 @@ import * as auth from "../../../lib/auth.js"
|
||||||
import { sendMail } from "../../../lib/mail.js"
|
import { sendMail } from "../../../lib/mail.js"
|
||||||
import {
|
import {
|
||||||
getAccount,
|
getAccount,
|
||||||
|
login,
|
||||||
noAPIAccess,
|
noAPIAccess,
|
||||||
requiresAccount,
|
requiresAccount,
|
||||||
requiresPermissions,
|
requiresScopes,
|
||||||
} from "../../../lib/middleware.js"
|
} from "../../../lib/middleware.js"
|
||||||
import { accountRatelimit } from "../../../lib/ratelimit.js"
|
import { accountRatelimit } from "../../../lib/ratelimit.js"
|
||||||
import config from "../../../lib/config.js"
|
import config from "../../../lib/config.js"
|
||||||
|
@ -58,13 +59,7 @@ export default function (files: Files) {
|
||||||
assign token
|
assign token
|
||||||
*/
|
*/
|
||||||
|
|
||||||
setCookie(ctx, "auth", auth.create(acc.id, 3 * 24 * 60 * 60 * 1000), {
|
login(ctx, acc.id)
|
||||||
path: "/",
|
|
||||||
sameSite: "Strict",
|
|
||||||
secure: true,
|
|
||||||
httpOnly: true,
|
|
||||||
maxAge: 3 * 24 * 60 * 60 * 1000,
|
|
||||||
})
|
|
||||||
return ctx.text("")
|
return ctx.text("")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -116,11 +111,7 @@ export default function (files: Files) {
|
||||||
assign token
|
assign token
|
||||||
*/
|
*/
|
||||||
|
|
||||||
setCookie(
|
login(ctx, newAcc)
|
||||||
ctx,
|
|
||||||
"auth",
|
|
||||||
auth.create(newAcc, 3 * 24 * 60 * 60 * 1000)
|
|
||||||
)
|
|
||||||
return ctx.text("")
|
return ctx.text("")
|
||||||
})
|
})
|
||||||
.catch(() => ServeError(ctx, 500, "internal server error"))
|
.catch(() => ServeError(ctx, 500, "internal server error"))
|
||||||
|
@ -138,7 +129,7 @@ export default function (files: Files) {
|
||||||
authRoutes.post(
|
authRoutes.post(
|
||||||
"/dfv",
|
"/dfv",
|
||||||
requiresAccount,
|
requiresAccount,
|
||||||
requiresPermissions("manage_files"),
|
requiresScopes("manage_files"),
|
||||||
// Used body-parser
|
// Used body-parser
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
const body = await ctx.req.json()
|
const body = await ctx.req.json()
|
||||||
|
@ -171,7 +162,7 @@ export default function (files: Files) {
|
||||||
let accId = acc.id
|
let accId = acc.id
|
||||||
|
|
||||||
auth.Db.data.filter((e) => e.account == accId).forEach((v) => {
|
auth.Db.data.filter((e) => e.account == accId).forEach((v) => {
|
||||||
auth.invalidate(v.token)
|
auth.invalidate(v.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
let cpl = () =>
|
let cpl = () =>
|
||||||
|
@ -462,7 +453,7 @@ export default function (files: Files) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof ctx.req.param("code") == "string" && vcode) {
|
if (typeof ctx.req.param("code") == "string" && vcode) {
|
||||||
setCookie(ctx, "auth", auth.create(vcode, 3 * 24 * 60 * 60 * 1000))
|
login(ctx, vcode)
|
||||||
let e = pwReset.get(vcode)?.expiry
|
let e = pwReset.get(vcode)?.expiry
|
||||||
if (e) clearTimeout(e)
|
if (e) clearTimeout(e)
|
||||||
pwReset.delete(vcode)
|
pwReset.delete(vcode)
|
||||||
|
@ -491,7 +482,7 @@ export default function (files: Files) {
|
||||||
Accounts.password.set(accId, body.password)
|
Accounts.password.set(accId, body.password)
|
||||||
|
|
||||||
auth.Db.data.filter((e) => e.account == accId).forEach((v) => {
|
auth.Db.data.filter((e) => e.account == accId).forEach((v) => {
|
||||||
auth.invalidate(v.token)
|
auth.invalidate(v.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (acc.email) {
|
if (acc.email) {
|
||||||
|
@ -518,7 +509,7 @@ export default function (files: Files) {
|
||||||
let accId = acc.id
|
let accId = acc.id
|
||||||
|
|
||||||
auth.Db.data.filter((e) => e.account == accId).forEach((v) => {
|
auth.Db.data.filter((e) => e.account == accId).forEach((v) => {
|
||||||
auth.invalidate(v.token)
|
auth.invalidate(v.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
return ctx.text("logged out all sessions")
|
return ctx.text("logged out all sessions")
|
||||||
|
@ -528,10 +519,10 @@ export default function (files: Files) {
|
||||||
authRoutes.get(
|
authRoutes.get(
|
||||||
"/me",
|
"/me",
|
||||||
requiresAccount,
|
requiresAccount,
|
||||||
requiresPermissions("user"),
|
requiresScopes("user"),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
let acc = ctx.get("account") as Accounts.Account
|
let acc = ctx.get("account") as Accounts.Account
|
||||||
let sessionToken = auth.tokenFor(ctx)!
|
let sessionToken = (await auth.tokenFor(ctx))!
|
||||||
let accId = acc.id
|
let accId = acc.id
|
||||||
return ctx.json({
|
return ctx.json({
|
||||||
...acc,
|
...acc,
|
||||||
|
@ -542,12 +533,12 @@ export default function (files: Files) {
|
||||||
(e.expire == null || e.expire > Date.now())
|
(e.expire == null || e.expire > Date.now())
|
||||||
).length,
|
).length,
|
||||||
sessionExpires: auth.Db.data.find(
|
sessionExpires: auth.Db.data.find(
|
||||||
(e) => e.token == sessionToken
|
(e) => e.id == sessionToken
|
||||||
)?.expire,
|
)?.expire,
|
||||||
password: undefined,
|
password: undefined,
|
||||||
email:
|
email:
|
||||||
auth.getType(sessionToken) == "User" ||
|
auth.getType(sessionToken) == "User" ||
|
||||||
auth.getPermissions(sessionToken)?.includes("email")
|
auth.getScopes(sessionToken)?.includes("email")
|
||||||
? acc.email
|
? acc.email
|
||||||
: undefined,
|
: undefined,
|
||||||
})
|
})
|
||||||
|
|
|
@ -5,7 +5,7 @@ import Files from "../../../lib/files.js"
|
||||||
import {
|
import {
|
||||||
getAccount,
|
getAccount,
|
||||||
requiresAccount,
|
requiresAccount,
|
||||||
requiresPermissions,
|
requiresScopes,
|
||||||
} from "../../../lib/middleware.js"
|
} from "../../../lib/middleware.js"
|
||||||
|
|
||||||
export let fileApiRoutes = new Hono<{
|
export let fileApiRoutes = new Hono<{
|
||||||
|
@ -20,7 +20,7 @@ export default function (files: Files) {
|
||||||
fileApiRoutes.get(
|
fileApiRoutes.get(
|
||||||
"/list",
|
"/list",
|
||||||
requiresAccount,
|
requiresAccount,
|
||||||
requiresPermissions("user"),
|
requiresScopes("user"),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
let acc = ctx.get("account") as Accounts.Account
|
let acc = ctx.get("account") as Accounts.Account
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@ export default function (files: Files) {
|
||||||
|
|
||||||
fileApiRoutes.post(
|
fileApiRoutes.post(
|
||||||
"/manage",
|
"/manage",
|
||||||
requiresPermissions("manage_files"),
|
requiresScopes("manage_files"),
|
||||||
async (ctx) => {
|
async (ctx) => {
|
||||||
let acc = ctx.get("account") as Accounts.Account
|
let acc = ctx.get("account") as Accounts.Account
|
||||||
const body = await ctx.req.json()
|
const body = await ctx.req.json()
|
||||||
|
|
|
@ -4,7 +4,7 @@ import * as auth from "../../../lib/auth.js"
|
||||||
import RangeParser, { type Range } from "range-parser"
|
import RangeParser, { type Range } from "range-parser"
|
||||||
import ServeError from "../../../lib/errors.js"
|
import ServeError from "../../../lib/errors.js"
|
||||||
import Files, { WebError } from "../../../lib/files.js"
|
import Files, { WebError } from "../../../lib/files.js"
|
||||||
import { getAccount, requiresPermissions } from "../../../lib/middleware.js"
|
import { getAccount, mirror, requiresScopes } from "../../../lib/middleware.js"
|
||||||
import {Readable} from "node:stream"
|
import {Readable} from "node:stream"
|
||||||
import type {ReadableStream as StreamWebReadable} from "node:stream/web"
|
import type {ReadableStream as StreamWebReadable} from "node:stream/web"
|
||||||
import formidable from "formidable"
|
import formidable from "formidable"
|
||||||
|
@ -37,17 +37,7 @@ export default function (files: Files, apiRoot: Hono) {
|
||||||
primaryApi.get("/cpt/:fileId/*", fileReader(apiRoot))
|
primaryApi.get("/cpt/:fileId/*", fileReader(apiRoot))
|
||||||
|
|
||||||
primaryApi.post("/upload", async (ctx) =>
|
primaryApi.post("/upload", async (ctx) =>
|
||||||
apiRoot.fetch(
|
mirror(apiRoot, ctx, "/api/v1/file", {method: "PUT"})
|
||||||
new Request(
|
|
||||||
(new URL(
|
|
||||||
`/api/v1/file`, ctx.req.raw.url)).href,
|
|
||||||
{
|
|
||||||
...ctx.req.raw,
|
|
||||||
method: "PUT"
|
|
||||||
}
|
|
||||||
),
|
|
||||||
ctx.env
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return primaryApi
|
return primaryApi
|
||||||
|
|
|
@ -16,7 +16,7 @@ import {
|
||||||
login,
|
login,
|
||||||
noAPIAccess,
|
noAPIAccess,
|
||||||
requiresAccount,
|
requiresAccount,
|
||||||
requiresPermissions,
|
requiresScopes,
|
||||||
scheme,
|
scheme,
|
||||||
} from "../../../../lib/middleware.js"
|
} from "../../../../lib/middleware.js"
|
||||||
import ServeError from "../../../../lib/errors.js"
|
import ServeError from "../../../../lib/errors.js"
|
||||||
|
@ -41,7 +41,7 @@ function getTargetToken(ctx: Context<HonoEnv, "/:token", BlankInput>) {
|
||||||
return auth.Db.data.find(
|
return auth.Db.data.find(
|
||||||
e =>
|
e =>
|
||||||
e.account == ctx.get("target").id
|
e.account == ctx.get("target").id
|
||||||
&& e.token == ctx.req.param("token")
|
&& e.id == ctx.req.param("token")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ export default function (files: Files) {
|
||||||
&& ctx.get("parsedScheme").has(e.type)
|
&& ctx.get("parsedScheme").has(e.type)
|
||||||
)
|
)
|
||||||
|
|
||||||
targets.forEach(e => auth.invalidate(e.token))
|
targets.forEach(e => auth.invalidate(e.id))
|
||||||
|
|
||||||
return ctx.text(`deleted ${targets.length} tokens`)
|
return ctx.text(`deleted ${targets.length} tokens`)
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ export default function (files: Files) {
|
||||||
})
|
})
|
||||||
|
|
||||||
router.delete("/:token", async (ctx) => {
|
router.delete("/:token", async (ctx) => {
|
||||||
auth.invalidate(ctx.get("targetToken").token)
|
auth.invalidate(ctx.get("targetToken").id)
|
||||||
return ctx.text(`deleted token ${ctx.req.param("token")}`)
|
return ctx.text(`deleted token ${ctx.req.param("token")}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ export default function (files: Files) {
|
||||||
? AuthSchemas.Scope.options
|
? AuthSchemas.Scope.options
|
||||||
: Array.from(new Set(params.scopes))
|
: Array.from(new Set(params.scopes))
|
||||||
)
|
)
|
||||||
return ctx.text(token)
|
return ctx.text(await auth.makeJwt(token.id))
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ import {
|
||||||
login,
|
login,
|
||||||
noAPIAccess,
|
noAPIAccess,
|
||||||
requiresAccount,
|
requiresAccount,
|
||||||
requiresPermissions,
|
requiresScopes,
|
||||||
scheme,
|
scheme,
|
||||||
verifyPoi,
|
verifyPoi,
|
||||||
} from "../../../../lib/middleware.js"
|
} from "../../../../lib/middleware.js"
|
||||||
|
@ -190,7 +190,7 @@ const validators: {
|
||||||
if (params.suspension)
|
if (params.suspension)
|
||||||
auth.Db.data
|
auth.Db.data
|
||||||
.filter(e => e.account == target.id)
|
.filter(e => e.account == target.id)
|
||||||
.forEach(e => auth.invalidate(e.token))
|
.forEach(e => auth.invalidate(e.id))
|
||||||
return params.suspension || undefined
|
return params.suspension || undefined
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -338,7 +338,7 @@ export default function (files: Files) {
|
||||||
return ServeError(ctx, 403, "invalid proof of identity provided")
|
return ServeError(ctx, 403, "invalid proof of identity provided")
|
||||||
|
|
||||||
auth.Db.data.filter((e) => e.account == target?.id).forEach((token) => {
|
auth.Db.data.filter((e) => e.account == target?.id).forEach((token) => {
|
||||||
auth.invalidate(token.token)
|
auth.invalidate(token.id)
|
||||||
})
|
})
|
||||||
|
|
||||||
await Accounts.deleteAccount(target.id)
|
await Accounts.deleteAccount(target.id)
|
||||||
|
@ -357,14 +357,14 @@ export default function (files: Files) {
|
||||||
|
|
||||||
router.get("/:user", async (ctx) => {
|
router.get("/:user", async (ctx) => {
|
||||||
let acc = ctx.get("target")
|
let acc = ctx.get("target")
|
||||||
let sessionToken = auth.tokenFor(ctx)!
|
let sessionToken = (await auth.tokenFor(ctx))!
|
||||||
|
|
||||||
return ctx.json({
|
return ctx.json({
|
||||||
...acc,
|
...acc,
|
||||||
password: undefined,
|
password: undefined,
|
||||||
email:
|
email:
|
||||||
auth.getType(sessionToken) == "User" ||
|
auth.getType(sessionToken) == "User" ||
|
||||||
auth.getPermissions(sessionToken)?.includes("email")
|
auth.getScopes(sessionToken)?.includes("email")
|
||||||
? acc.email
|
? acc.email
|
||||||
: undefined,
|
: undefined,
|
||||||
activeSessions: auth.Db.data.filter(
|
activeSessions: auth.Db.data.filter(
|
||||||
|
|
|
@ -16,7 +16,7 @@ import {
|
||||||
login,
|
login,
|
||||||
noAPIAccess,
|
noAPIAccess,
|
||||||
requiresAccount,
|
requiresAccount,
|
||||||
requiresPermissions,
|
requiresScopes,
|
||||||
requiresTarget,
|
requiresTarget,
|
||||||
scheme,
|
scheme,
|
||||||
} from "../../../../lib/middleware.js"
|
} from "../../../../lib/middleware.js"
|
||||||
|
|
|
@ -4,7 +4,7 @@ import * as auth from "../../../../lib/auth.js"
|
||||||
import RangeParser, { type Range } from "range-parser"
|
import RangeParser, { type Range } from "range-parser"
|
||||||
import ServeError from "../../../../lib/errors.js"
|
import ServeError from "../../../../lib/errors.js"
|
||||||
import Files, { WebError } from "../../../../lib/files.js"
|
import Files, { WebError } from "../../../../lib/files.js"
|
||||||
import { getAccount, requiresAccount, requiresPermissions, scheme } from "../../../../lib/middleware.js"
|
import { getAccount, requiresAccount, requiresScopes, scheme } from "../../../../lib/middleware.js"
|
||||||
import {Readable} from "node:stream"
|
import {Readable} from "node:stream"
|
||||||
import type {ReadableStream as StreamWebReadable} from "node:stream/web"
|
import type {ReadableStream as StreamWebReadable} from "node:stream/web"
|
||||||
import formidable from "formidable"
|
import formidable from "formidable"
|
||||||
|
@ -28,7 +28,7 @@ export default function(files: Files) {
|
||||||
router.on(
|
router.on(
|
||||||
["PUT", "POST"],
|
["PUT", "POST"],
|
||||||
"/",
|
"/",
|
||||||
requiresPermissions("manage_files"),
|
requiresScopes("manage_files"),
|
||||||
(ctx) => { return new Promise((resolve,reject) => {
|
(ctx) => { return new Promise((resolve,reject) => {
|
||||||
ctx.env.incoming.removeAllListeners("data") // remove hono's buffering
|
ctx.env.incoming.removeAllListeners("data") // remove hono's buffering
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import * as auth from "../../../../lib/auth.js"
|
||||||
import RangeParser, { type Range } from "range-parser"
|
import RangeParser, { type Range } from "range-parser"
|
||||||
import ServeError from "../../../../lib/errors.js"
|
import ServeError from "../../../../lib/errors.js"
|
||||||
import Files, { WebError } from "../../../../lib/files.js"
|
import Files, { WebError } from "../../../../lib/files.js"
|
||||||
import { getAccount, requiresPermissions } from "../../../../lib/middleware.js"
|
import { getAccount, requiresScopes } from "../../../../lib/middleware.js"
|
||||||
import {Readable} from "node:stream"
|
import {Readable} from "node:stream"
|
||||||
import type {ReadableStream as StreamWebReadable} from "node:stream/web"
|
import type {ReadableStream as StreamWebReadable} from "node:stream/web"
|
||||||
import formidable from "formidable"
|
import formidable from "formidable"
|
||||||
|
@ -50,10 +50,12 @@ export default function(files: Files, apiRoot: Hono) {
|
||||||
return ServeError(ctx, 403, "you do not own this file")
|
return ServeError(ctx, 403, "you do not own this file")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let token = (await auth.tokenFor(ctx))!
|
||||||
|
|
||||||
if (
|
if (
|
||||||
auth.getType(auth.tokenFor(ctx)!) != "User" &&
|
auth.getType(token) != "User" &&
|
||||||
auth
|
auth
|
||||||
.getPermissions(auth.tokenFor(ctx)!)!
|
.getScopes(token)!
|
||||||
.includes("private")
|
.includes("private")
|
||||||
) {
|
) {
|
||||||
return ServeError(ctx, 403, "insufficient permissions")
|
return ServeError(ctx, 403, "insufficient permissions")
|
||||||
|
|
|
@ -58,17 +58,13 @@ export default function (files: Files) {
|
||||||
return ctx.text("logged in")
|
return ctx.text("logged in")
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get("/", requiresAccount, ctx => {
|
router.get("/", requiresAccount, async ctx => {
|
||||||
let sessionToken = auth.tokenFor(ctx)
|
let sessionToken = (await auth.tokenFor(ctx))!
|
||||||
return ctx.json({
|
return ctx.redirect(`/api/v1`)
|
||||||
expiry: auth.Db.data.find(
|
|
||||||
(e) => e.token == sessionToken
|
|
||||||
)?.expire,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
router.delete("/", requiresAccount, (ctx) => {
|
router.delete("/", requiresAccount, async ctx => {
|
||||||
auth.invalidate(auth.tokenFor(ctx)!)
|
auth.invalidate((await auth.tokenFor(ctx))!)
|
||||||
return ctx.text("logged out")
|
return ctx.text("logged out")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue