diff --git a/.github/workflows/api-test.yml b/.github/workflows/api-test.yml index fa19c07b..46e170f0 100644 --- a/.github/workflows/api-test.yml +++ b/.github/workflows/api-test.yml @@ -36,5 +36,4 @@ jobs: working-directory: ./api run: bun test env: - JWT_SECRET: "TODO" POSTGRES_SERVER: localhost diff --git a/api/src/auth.ts b/api/src/auth.ts index eac5eb7e..ff6bd3e5 100644 --- a/api/src/auth.ts +++ b/api/src/auth.ts @@ -14,13 +14,18 @@ const jwks = createRemoteJWKSet( const Jwt = t.Object({ sub: t.String({ description: "User id" }), - username: t.String(), sid: t.String({ description: "Session id" }), + username: t.String(), permissions: t.Array(t.String()), }); const validator = TypeCompiler.Compile(Jwt); export const auth = new Elysia({ name: "auth" }) + .guard({ + headers: t.Object({ + authorization: t.TemplateLiteral("Bearer ${string}"), + }), + }) .macro({ permissions(perms: string[]) { return { diff --git a/api/src/base.ts b/api/src/base.ts index 86a96835..d6de2a49 100644 --- a/api/src/base.ts +++ b/api/src/base.ts @@ -11,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 { KError } from "./models/error"; +import type { KError } from "./models/error"; export const base = new Elysia({ name: "base" }) .onError(({ code, error }) => { @@ -61,11 +61,12 @@ export const app = new Elysia({ prefix }) detail: { security: [{ bearer: ["core.read"] }, { api: ["core.read"] }], }, - response: { - 401: { ...KError, description: "" }, - 403: { ...KError, description: "" }, - }, - perms: ["core.read"], + // See https://github.com/elysiajs/elysia/issues/1158 + // response: { + // 401: { ...KError, description: "" }, + // 403: { ...KError, description: "" }, + // }, + permissions: ["core.read"], }, (app) => app @@ -84,11 +85,12 @@ export const app = new Elysia({ prefix }) detail: { security: [{ bearer: ["core.write"] }, { api: ["core.write"] }], }, - response: { - 401: { ...KError, description: "" }, - 403: { ...KError, description: "" }, - }, - perms: ["core.read"], + // See https://github.com/elysiajs/elysia/issues/1158 + // response: { + // 401: { ...KError, description: "" }, + // 403: { ...KError, description: "" }, + // }, + permissions: ["core.write"], }, (app) => app.use(videosH).use(seed), ); diff --git a/api/tests/helpers/jwt.ts b/api/tests/helpers/jwt.ts new file mode 100644 index 00000000..80efc31b --- /dev/null +++ b/api/tests/helpers/jwt.ts @@ -0,0 +1,17 @@ +import { SignJWT } from "jose"; + +export async function getJwtHeaders() { + const jwt = await new SignJWT({ + sub: "39158be0-3f59-4c45-b00d-d25b3bc2b884", + sid: "04ac7ecc-255b-481d-b0c8-537c1578e3d5", + username: "test-username", + permissions: ["core.read", "core.write"], + }) + .setProtectedHeader({ alg: "HS256" }) + .setIssuedAt() + .setIssuer(process.env.JWT_ISSUER!) + .setExpirationTime("2h") + .sign(new TextEncoder().encode(process.env.JWT_SECRET)); + + return { Authorization: `Bearer ${jwt}` }; +} diff --git a/api/tests/helpers/movies-helper.ts b/api/tests/helpers/movies-helper.ts index c7ddba29..85b09916 100644 --- a/api/tests/helpers/movies-helper.ts +++ b/api/tests/helpers/movies-helper.ts @@ -1,6 +1,7 @@ import { buildUrl } from "tests/utils"; import { app } from "~/base"; import type { SeedMovie } from "~/models/movie"; +import { getJwtHeaders } from "./jwt"; export const getMovie = async ( id: string, @@ -15,8 +16,9 @@ export const getMovie = async ( headers: langs ? { "Accept-Language": langs, + ...(await getJwtHeaders()), } - : {}, + : await getJwtHeaders(), }), ); const body = await resp.json(); @@ -41,8 +43,9 @@ export const getMovies = async ({ headers: langs ? { "Accept-Language": langs, + ...(await getJwtHeaders()), } - : {}, + : await getJwtHeaders(), }), ); const body = await resp.json(); @@ -56,6 +59,7 @@ export const createMovie = async (movie: SeedMovie) => { body: JSON.stringify(movie), headers: { "Content-Type": "application/json", + ...(await getJwtHeaders()), }, }), ); diff --git a/api/tests/helpers/series-helper.ts b/api/tests/helpers/series-helper.ts index 776c09eb..7004278e 100644 --- a/api/tests/helpers/series-helper.ts +++ b/api/tests/helpers/series-helper.ts @@ -1,6 +1,7 @@ import { buildUrl } from "tests/utils"; import { app } from "~/base"; import type { SeedSerie } from "~/models/serie"; +import { getJwtHeaders } from "./jwt"; export const createSerie = async (serie: SeedSerie) => { const resp = await app.handle( @@ -9,6 +10,7 @@ export const createSerie = async (serie: SeedSerie) => { body: JSON.stringify(serie), headers: { "Content-Type": "application/json", + ...(await getJwtHeaders()), }, }), ); @@ -29,8 +31,9 @@ export const getSerie = async ( headers: langs ? { "Accept-Language": langs, + ...(await getJwtHeaders()), } - : {}, + : await getJwtHeaders(), }), ); const body = await resp.json(); @@ -58,8 +61,9 @@ export const getSeasons = async ( headers: langs ? { "Accept-Language": langs, + ...(await getJwtHeaders()), } - : {}, + : await getJwtHeaders(), }), ); const body = await resp.json(); @@ -87,8 +91,9 @@ export const getEntries = async ( headers: langs ? { "Accept-Language": langs, + ...(await getJwtHeaders()), } - : {}, + : await getJwtHeaders(), }), ); const body = await resp.json(); @@ -108,6 +113,7 @@ export const getExtras = async ( const resp = await app.handle( new Request(buildUrl(`series/${serie}/extras`, opts), { method: "GET", + headers: await getJwtHeaders(), }), ); const body = await resp.json(); @@ -124,6 +130,7 @@ export const getUnknowns = async (opts: { const resp = await app.handle( new Request(buildUrl("unknowns", opts), { method: "GET", + headers: await getJwtHeaders(), }), ); const body = await resp.json(); @@ -147,8 +154,9 @@ export const getNews = async ({ headers: langs ? { "Accept-Language": langs, + ...(await getJwtHeaders()), } - : {}, + : await getJwtHeaders(), }), ); const body = await resp.json(); diff --git a/api/tests/helpers/staff-helper.ts b/api/tests/helpers/staff-helper.ts index a4a4b99b..dbb2a613 100644 --- a/api/tests/helpers/staff-helper.ts +++ b/api/tests/helpers/staff-helper.ts @@ -1,10 +1,12 @@ import { buildUrl } from "tests/utils"; import { app } from "~/base"; +import { getJwtHeaders } from "./jwt"; export const getStaff = async (id: string, query: {}) => { const resp = await app.handle( new Request(buildUrl(`staff/${id}`, query), { method: "GET", + headers: await getJwtHeaders(), }), ); const body = await resp.json(); @@ -32,8 +34,9 @@ export const getStaffRoles = async ( headers: langs ? { "Accept-Language": langs, + ...(await getJwtHeaders()), } - : {}, + : await getJwtHeaders(), }), ); const body = await resp.json(); @@ -52,6 +55,7 @@ export const getSerieStaff = async ( const resp = await app.handle( new Request(buildUrl(`series/${serie}/staff`, opts), { method: "GET", + headers: await getJwtHeaders(), }), ); const body = await resp.json(); @@ -70,6 +74,7 @@ export const getMovieStaff = async ( const resp = await app.handle( new Request(buildUrl(`movies/${movie}/staff`, opts), { method: "GET", + headers: await getJwtHeaders(), }), ); const body = await resp.json(); diff --git a/api/tests/helpers/studio-helper.ts b/api/tests/helpers/studio-helper.ts index bc6ccfe8..711fa26a 100644 --- a/api/tests/helpers/studio-helper.ts +++ b/api/tests/helpers/studio-helper.ts @@ -1,5 +1,6 @@ import { buildUrl } from "tests/utils"; import { app } from "~/base"; +import { getJwtHeaders } from "./jwt"; export const getStudio = async ( id: string, @@ -11,8 +12,9 @@ export const getStudio = async ( headers: langs ? { "Accept-Language": langs, + ...await getJwtHeaders() } - : {}, + : await getJwtHeaders() }), ); const body = await resp.json(); @@ -40,8 +42,9 @@ export const getShowsByStudio = async ( headers: langs ? { "Accept-Language": langs, + ...await getJwtHeaders() } - : {}, + : await getJwtHeaders() }), ); const body = await resp.json(); diff --git a/api/tests/helpers/videos-helper.ts b/api/tests/helpers/videos-helper.ts index 74232b7c..380cd81d 100644 --- a/api/tests/helpers/videos-helper.ts +++ b/api/tests/helpers/videos-helper.ts @@ -1,6 +1,7 @@ import { buildUrl } from "tests/utils"; import { app } from "~/base"; import type { SeedVideo } from "~/models/video"; +import { getJwtHeaders } from "./jwt"; export const createVideo = async (video: SeedVideo | SeedVideo[]) => { const resp = await app.handle( @@ -9,6 +10,7 @@ export const createVideo = async (video: SeedVideo | SeedVideo[]) => { body: JSON.stringify(Array.isArray(video) ? video : [video]), headers: { "Content-Type": "application/json", + ...(await getJwtHeaders()), }, }), ); diff --git a/api/tests/movies/get-all-movies-with-null.test.ts b/api/tests/movies/get-all-movies-with-null.test.ts index 5b633510..2cba7fb6 100644 --- a/api/tests/movies/get-all-movies-with-null.test.ts +++ b/api/tests/movies/get-all-movies-with-null.test.ts @@ -1,4 +1,5 @@ import { beforeAll, describe, expect, it } from "bun:test"; +import { getJwtHeaders } from "tests/helpers/jwt"; import { expectStatus } from "tests/utils"; import { db } from "~/db"; import { shows } from "~/db/schema"; @@ -10,8 +11,8 @@ import { app, createMovie, getMovies } from "../helpers"; beforeAll(async () => { await db.delete(shows); for (const movie of [bubble, dune1984, dune]) { - const [ret, _] = await createMovie(movie); - expect(ret.status).toBe(201); + const [ret, body] = await createMovie(movie); + expectStatus(ret, body).toBe(201); } }); @@ -73,7 +74,9 @@ describe("with a null value", () => { ), }); - resp = await app.handle(new Request(next)); + resp = await app.handle( + new Request(next, { headers: await getJwtHeaders() }), + ); body = await resp.json(); expectStatus(resp, body).toBe(200); @@ -120,7 +123,9 @@ describe("with a null value", () => { ), }); - resp = await app.handle(new Request(next)); + resp = await app.handle( + new Request(next, { headers: await getJwtHeaders() }), + ); body = await resp.json(); expectStatus(resp, body).toBe(200); diff --git a/api/tests/movies/get-all-movies.test.ts b/api/tests/movies/get-all-movies.test.ts index 9eb9f2af..94fef089 100644 --- a/api/tests/movies/get-all-movies.test.ts +++ b/api/tests/movies/get-all-movies.test.ts @@ -1,4 +1,5 @@ import { beforeAll, describe, expect, it } from "bun:test"; +import { getJwtHeaders } from "tests/helpers/jwt"; import { expectStatus } from "tests/utils"; import { db } from "~/db"; import { shows } from "~/db/schema"; @@ -71,7 +72,9 @@ describe("Get all movies", () => { }); expectStatus(resp, body).toBe(200); - resp = await app.handle(new Request(body.next)); + resp = await app.handle( + new Request(body.next, { headers: await getJwtHeaders() }), + ); body = await resp.json(); expectStatus(resp, body).toBe(200); @@ -104,7 +107,9 @@ describe("Get all movies", () => { ), }); - resp = await app.handle(new Request(next)); + resp = await app.handle( + new Request(next, { headers: await getJwtHeaders() }), + ); body = await resp.json(); expectStatus(resp, body).toBe(200); @@ -160,7 +165,9 @@ describe("Get all movies", () => { expect(items.length).toBe(1); expect(items[0].id).toBe(expectedIds[0]); // Get Second Page - resp = await app.handle(new Request(body.next)); + resp = await app.handle( + new Request(body.next, { headers: await getJwtHeaders() }), + ); body = await resp.json(); expectStatus(resp, body).toBe(200); @@ -175,7 +182,9 @@ describe("Get all movies", () => { }); expectStatus(resp, body).toBe(200); - const resp2 = await app.handle(new Request(body.next)); + const resp2 = await app.handle( + new Request(body.next, { headers: await getJwtHeaders() }), + ); const body2 = await resp2.json(); expectStatus(resp2, body).toBe(200); @@ -187,7 +196,9 @@ describe("Get all movies", () => { it("Get /random", async () => { const resp = await app.handle( - new Request("http://localhost/movies/random"), + new Request("http://localhost/movies/random", { + headers: await getJwtHeaders(), + }), ); expect(resp.status).toBe(302); const location = resp.headers.get("location")!; diff --git a/api/tests/setup.ts b/api/tests/setup.ts index ab07c363..e9b014f1 100644 --- a/api/tests/setup.ts +++ b/api/tests/setup.ts @@ -1,6 +1,9 @@ import { beforeAll } from "bun:test"; import { migrate } from "~/db"; +process.env.JWT_SECRET = "this is a secret"; +process.env.JWT_ISSUER = "https://kyoo.zoriya.dev"; + beforeAll(async () => { await migrate(); });