165 lines
5.6 KiB
TypeScript
165 lines
5.6 KiB
TypeScript
import { describe, test, expect, mock, beforeEach } from "bun:test";
|
|
import { Hono } from "hono";
|
|
|
|
// We test the authMiddleware in isolation by importing and mounting it on a
|
|
// minimal Hono app, without touching the real session/user layer.
|
|
|
|
// Mock the managers and modules that authMiddleware depends on
|
|
mock.module("../../../src/managers/sessions", () => ({
|
|
sessions: {
|
|
fetch: mock(),
|
|
},
|
|
}));
|
|
|
|
mock.module("../../../src/modules/globalEvents", () => ({
|
|
events: {
|
|
emit: mock(),
|
|
on: mock(),
|
|
any: mock(),
|
|
},
|
|
}));
|
|
|
|
import { authMiddleware } from "../../../src/api/middleware/authorization";
|
|
import { sessions } from "../../../src/managers/sessions";
|
|
import { apiResponse } from "../../../src/modules/api-utils/apiResponse";
|
|
|
|
function createTestApp(permParams?: Parameters<typeof authMiddleware>[0]) {
|
|
const app = new Hono();
|
|
app.onError((err, c) => {
|
|
const response = apiResponse.error(err);
|
|
return c.json(response, response.status);
|
|
});
|
|
app.use("*", authMiddleware(permParams));
|
|
app.get("/test", (c) => c.json({ ok: true }));
|
|
return app;
|
|
}
|
|
|
|
describe("authMiddleware", () => {
|
|
beforeEach(() => {
|
|
// Reset mocks
|
|
(sessions.fetch as any).mockReset?.();
|
|
});
|
|
|
|
// -------------------------------------------------------------------
|
|
// Missing authorization header
|
|
// -------------------------------------------------------------------
|
|
test("rejects requests without authorization header", async () => {
|
|
const app = createTestApp();
|
|
const res = await app.request("/test");
|
|
expect(res.status).toBe(401);
|
|
const body: any = await res.json();
|
|
expect(body.error).toBe("AuthorizationError");
|
|
expect(body.message).toContain("authorization");
|
|
});
|
|
|
|
// -------------------------------------------------------------------
|
|
// Malformed authorization header
|
|
// -------------------------------------------------------------------
|
|
test("rejects malformed authorization header", async () => {
|
|
const app = createTestApp();
|
|
const res = await app.request("/test", {
|
|
headers: { Authorization: "foobar" },
|
|
});
|
|
expect(res.status).toBe(401);
|
|
const body: any = await res.json();
|
|
expect(body.error).toBe("AuthorizationError");
|
|
expect(body.message).toContain("malformed");
|
|
});
|
|
|
|
test("rejects authorization missing token value", async () => {
|
|
const app = createTestApp();
|
|
const res = await app.request("/test", {
|
|
headers: { Authorization: "Bearer " },
|
|
});
|
|
expect(res.status).toBeGreaterThanOrEqual(400);
|
|
const body: any = await res.json();
|
|
expect(body.successful).toBe(false);
|
|
});
|
|
|
|
// -------------------------------------------------------------------
|
|
// Forbidden auth types
|
|
// -------------------------------------------------------------------
|
|
test("rejects forbidden auth types", async () => {
|
|
const app = createTestApp({ forbiddenAuthTypes: ["Key"] });
|
|
const res = await app.request("/test", {
|
|
headers: { Authorization: "Key aaa.bbb.ccc" },
|
|
});
|
|
expect(res.status).toBe(403);
|
|
const body: any = await res.json();
|
|
expect(body.error).toBe("NonpermittedAuthType");
|
|
});
|
|
|
|
// -------------------------------------------------------------------
|
|
// Valid token flow
|
|
// -------------------------------------------------------------------
|
|
test("calls sessions.fetch with access token", async () => {
|
|
const mockUser = {
|
|
hasPermission: mock(() => Promise.resolve(true)),
|
|
};
|
|
const mockSession = {
|
|
fetchUser: mock(() => Promise.resolve(mockUser)),
|
|
};
|
|
(sessions.fetch as any).mockResolvedValue?.(mockSession) ??
|
|
((sessions as any).fetch = mock(() => Promise.resolve(mockSession)));
|
|
|
|
const app = createTestApp();
|
|
const res = await app.request("/test", {
|
|
headers: { Authorization: "Bearer aaa.bbb.ccc" },
|
|
});
|
|
|
|
// If sessions.fetch resolves, the middleware should pass through
|
|
expect(res.status).toBe(200);
|
|
});
|
|
|
|
// -------------------------------------------------------------------
|
|
// Permission checking
|
|
// -------------------------------------------------------------------
|
|
test("rejects when user lacks required permission", async () => {
|
|
const mockUser = {
|
|
hasPermission: mock(() => Promise.resolve(false)),
|
|
};
|
|
const mockSession = {
|
|
fetchUser: mock(() => Promise.resolve(mockUser)),
|
|
};
|
|
(sessions as any).fetch = mock(() => Promise.resolve(mockSession));
|
|
|
|
const app = createTestApp({ permissions: ["admin.super"] });
|
|
const res = await app.request("/test", {
|
|
headers: { Authorization: "Bearer aaa.bbb.ccc" },
|
|
});
|
|
expect(res.status).toBe(403);
|
|
const body: any = await res.json();
|
|
expect(body.message).toContain("permission");
|
|
});
|
|
|
|
test("allows when user has all required permissions", async () => {
|
|
const mockUser = {
|
|
hasPermission: mock(() => Promise.resolve(true)),
|
|
};
|
|
const mockSession = {
|
|
fetchUser: mock(() => Promise.resolve(mockUser)),
|
|
};
|
|
(sessions as any).fetch = mock(() => Promise.resolve(mockSession));
|
|
|
|
const app = createTestApp({
|
|
permissions: ["company.fetch", "company.list"],
|
|
});
|
|
const res = await app.request("/test", {
|
|
headers: { Authorization: "Bearer aaa.bbb.ccc" },
|
|
});
|
|
expect(res.status).toBe(200);
|
|
});
|
|
|
|
test("passes through when no permissions required", async () => {
|
|
const mockUser = { hasPermission: mock(() => Promise.resolve(true)) };
|
|
const mockSession = { fetchUser: mock(() => Promise.resolve(mockUser)) };
|
|
(sessions as any).fetch = mock(() => Promise.resolve(mockSession));
|
|
|
|
const app = createTestApp();
|
|
const res = await app.request("/test", {
|
|
headers: { Authorization: "Bearer aaa.bbb.ccc" },
|
|
});
|
|
expect(res.status).toBe(200);
|
|
});
|
|
});
|