Check for permissions on each routes

This commit is contained in:
Zoe Roux 2025-04-05 16:50:35 +02:00
parent 8110f7de66
commit 49f700ca6e
No known key found for this signature in database
2 changed files with 64 additions and 29 deletions

View File

@ -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 { createRemoteJWKSet, jwtVerify } from "jose";
import { KError } from "./models/error";
const jwtSecret = process.env.JWT_SECRET const jwtSecret = process.env.JWT_SECRET
? new TextEncoder().encode(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" }), sub: t.String({ description: "User id" }),
username: t.String(), username: t.String(),
sid: t.String({ description: "Session id" }), 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" }) 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({ .macro({
permissions(perms: string[]) { permissions(perms: string[]) {
return { return {
resolve: async ({ headers: { authorization }, error }) => { resolve: async ({ headers: { authorization }, error }) => {
console.log(process.env.JWT_ISSUER);
const bearer = authorization?.slice(7); 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 // @ts-expect-error ts can't understand that there's two overload idk why
const { payload } = await jwtVerify(bearer, jwtSecret ?? jwks, { const { payload } = await jwtVerify(bearer, jwtSecret ?? jwks, {
issuer: process.env.JWT_ISSUER, issuer: process.env.JWT_ISSUER,
}); });
// TODO: use perms const jwt = validator.Decode(payload);
return { jwt: validator.Decode<typeof Jwt>(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 };
}, },
}; };
}, },

View File

@ -1,4 +1,5 @@
import { Elysia, t } from "elysia"; import { Elysia, t } from "elysia";
import { auth } from "./auth";
import { entriesH } from "./controllers/entries"; import { entriesH } from "./controllers/entries";
import { imagesH } from "./controllers/images"; import { imagesH } from "./controllers/images";
import { seasonsH } from "./controllers/seasons"; import { seasonsH } from "./controllers/seasons";
@ -10,7 +11,7 @@ import { showsH } from "./controllers/shows/shows";
import { staffH } from "./controllers/staff"; import { staffH } from "./controllers/staff";
import { studiosH } from "./controllers/studios"; import { studiosH } from "./controllers/studios";
import { videosH } from "./controllers/videos"; import { videosH } from "./controllers/videos";
import type { KError } from "./models/error"; import { KError } from "./models/error";
export const base = new Elysia({ name: "base" }) export const base = new Elysia({ name: "base" })
.onError(({ code, error }) => { .onError(({ code, error }) => {
@ -53,14 +54,41 @@ export const base = new Elysia({ name: "base" })
export const prefix = process.env.KYOO_PREFIX ?? ""; export const prefix = process.env.KYOO_PREFIX ?? "";
export const app = new Elysia({ prefix }) export const app = new Elysia({ prefix })
.use(base) .use(base)
.use(showsH) .use(auth)
.use(movies) .guard(
.use(series) {
.use(collections) // Those are not applied for now. See https://github.com/elysiajs/elysia/issues/1139
.use(entriesH) detail: {
.use(seasonsH) security: [{ bearer: ["core.read"] }, { api: ["core.read"] }],
.use(studiosH) },
.use(staffH) response: {
.use(videosH) 401: { ...KError, description: "" },
.use(imagesH) 403: { ...KError, description: "" },
.use(seed); },
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),
);