From 49f700ca6ecaf1f2425a12947a05ca22b96f397b Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sat, 5 Apr 2025 16:50:35 +0200 Subject: [PATCH] Check for permissions on each routes --- api/src/auth.ts | 41 ++++++++++++++++++++++---------------- api/src/base.ts | 52 +++++++++++++++++++++++++++++++++++++------------ 2 files changed, 64 insertions(+), 29 deletions(-) diff --git a/api/src/auth.ts b/api/src/auth.ts index abae1100..eac5eb7e 100644 --- a/api/src/auth.ts +++ b/api/src/auth.ts @@ -1,6 +1,6 @@ -import Elysia, { getSchemaValidator, t } from "elysia"; +import { TypeCompiler } from "@sinclair/typebox/compiler"; +import Elysia, { t } from "elysia"; import { createRemoteJWKSet, jwtVerify } from "jose"; -import { KError } from "./models/error"; const jwtSecret = process.env.JWT_SECRET ? new TextEncoder().encode(process.env.JWT_SECRET) @@ -16,33 +16,40 @@ const Jwt = t.Object({ sub: t.String({ description: "User id" }), username: t.String(), sid: t.String({ description: "Session id" }), + permissions: t.Array(t.String()), }); -const validator = getSchemaValidator(Jwt); +const validator = TypeCompiler.Compile(Jwt); export const auth = new Elysia({ name: "auth" }) - .guard({ - // Those are not applied for now. See https://github.com/elysiajs/elysia/issues/1139 - detail: { - security: [{ bearer: ["read"] }, { api: ["read"] }], - }, - response: { - 401: { ...KError, description: "" }, - 403: { ...KError, description: "" }, - }, - }) .macro({ permissions(perms: string[]) { return { resolve: async ({ headers: { authorization }, error }) => { - console.log(process.env.JWT_ISSUER); const bearer = authorization?.slice(7); - if (!bearer) return { jwt: false }; + if (!bearer) { + return error(500, { + status: 500, + message: "No jwt, auth server configuration error.", + }); + } + // @ts-expect-error ts can't understand that there's two overload idk why const { payload } = await jwtVerify(bearer, jwtSecret ?? jwks, { issuer: process.env.JWT_ISSUER, }); - // TODO: use perms - return { jwt: validator.Decode(payload) }; + const jwt = validator.Decode(payload); + + for (const perm of perms) { + if (!jwt.permissions.includes(perm)) { + return error(403, { + status: 403, + message: `Missing permission: '${perm}'.`, + details: { current: jwt.permissions, required: perms }, + }); + } + } + + return { jwt }; }, }; }, diff --git a/api/src/base.ts b/api/src/base.ts index 248ac1a1..86a96835 100644 --- a/api/src/base.ts +++ b/api/src/base.ts @@ -1,4 +1,5 @@ import { Elysia, t } from "elysia"; +import { auth } from "./auth"; import { entriesH } from "./controllers/entries"; import { imagesH } from "./controllers/images"; import { seasonsH } from "./controllers/seasons"; @@ -10,7 +11,7 @@ import { showsH } from "./controllers/shows/shows"; import { staffH } from "./controllers/staff"; import { studiosH } from "./controllers/studios"; import { videosH } from "./controllers/videos"; -import type { KError } from "./models/error"; +import { KError } from "./models/error"; export const base = new Elysia({ name: "base" }) .onError(({ code, error }) => { @@ -53,14 +54,41 @@ export const base = new Elysia({ name: "base" }) export const prefix = process.env.KYOO_PREFIX ?? ""; export const app = new Elysia({ prefix }) .use(base) - .use(showsH) - .use(movies) - .use(series) - .use(collections) - .use(entriesH) - .use(seasonsH) - .use(studiosH) - .use(staffH) - .use(videosH) - .use(imagesH) - .use(seed); + .use(auth) + .guard( + { + // Those are not applied for now. See https://github.com/elysiajs/elysia/issues/1139 + detail: { + security: [{ bearer: ["core.read"] }, { api: ["core.read"] }], + }, + response: { + 401: { ...KError, description: "" }, + 403: { ...KError, description: "" }, + }, + perms: ["core.read"], + }, + (app) => + app + .use(showsH) + .use(movies) + .use(series) + .use(collections) + .use(entriesH) + .use(seasonsH) + .use(studiosH) + .use(staffH) + .use(imagesH), + ) + .guard( + { + detail: { + security: [{ bearer: ["core.write"] }, { api: ["core.write"] }], + }, + response: { + 401: { ...KError, description: "" }, + 403: { ...KError, description: "" }, + }, + perms: ["core.read"], + }, + (app) => app.use(videosH).use(seed), + );