This commit is contained in:
338
backend/__tests__/middlewares/permission.spec.ts
Normal file
338
backend/__tests__/middlewares/permission.spec.ts
Normal file
@@ -0,0 +1,338 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||||
import { Response } from "express";
|
||||
import { verifyPermissions } from "../../src/middlewares/permission";
|
||||
import { EndpointMetadata } from "@monkeytype/contracts/util/api";
|
||||
import * as Misc from "../../src/utils/misc";
|
||||
import * as AdminUids from "../../src/dal/admin-uids";
|
||||
import * as UserDal from "../../src/dal/user";
|
||||
import MonkeyError from "../../src/utils/error";
|
||||
import { DecodedToken } from "../../src/middlewares/auth";
|
||||
import { TsRestRequest } from "../../src/api/types";
|
||||
import { enableMonkeyErrorExpects } from "../__testData__/monkey-error";
|
||||
|
||||
enableMonkeyErrorExpects();
|
||||
const uid = "123456789";
|
||||
|
||||
describe("permission middleware", () => {
|
||||
const handler = verifyPermissions();
|
||||
const res: Response = {} as any;
|
||||
const next = vi.fn();
|
||||
const getPartialUserMock = vi.spyOn(UserDal, "getPartialUser");
|
||||
const isAdminMock = vi.spyOn(AdminUids, "isAdmin");
|
||||
const isDevMock = vi.spyOn(Misc, "isDevEnvironment");
|
||||
|
||||
beforeEach(() => {
|
||||
next.mockClear();
|
||||
getPartialUserMock.mockClear().mockResolvedValue({} as any);
|
||||
isDevMock.mockClear().mockReturnValue(false);
|
||||
isAdminMock.mockClear().mockResolvedValue(false);
|
||||
});
|
||||
afterEach(() => {
|
||||
//next function must only be called once
|
||||
expect(next).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("should bypass without requiredPermission", async () => {
|
||||
//GIVEN
|
||||
const req = givenRequest({});
|
||||
//WHEN
|
||||
await handler(req, res, next);
|
||||
|
||||
//THEN
|
||||
expect(next).toHaveBeenCalledWith();
|
||||
});
|
||||
it("should bypass with empty requiredPermission", async () => {
|
||||
//GIVEN
|
||||
const req = givenRequest({ requirePermission: [] });
|
||||
//WHEN
|
||||
await handler(req, res, next);
|
||||
|
||||
//THE
|
||||
expect(next).toHaveBeenCalledWith();
|
||||
});
|
||||
|
||||
describe("admin check", () => {
|
||||
const requireAdminPermission: EndpointMetadata = {
|
||||
requirePermission: "admin",
|
||||
};
|
||||
|
||||
it("should fail without authentication", async () => {
|
||||
//GIVEN
|
||||
const req = givenRequest(requireAdminPermission);
|
||||
//WHEN
|
||||
await handler(req, res, next);
|
||||
|
||||
//THEN
|
||||
expect(next).toHaveBeenCalledWith(
|
||||
expect.toMatchMonkeyError(
|
||||
new MonkeyError(403, "You don't have permission to do this."),
|
||||
),
|
||||
);
|
||||
});
|
||||
it("should pass without authentication if publicOnDev on dev", async () => {
|
||||
//GIVEN
|
||||
isDevMock.mockReturnValue(true);
|
||||
const req = givenRequest(
|
||||
{
|
||||
...requireAdminPermission,
|
||||
authenticationOptions: { isPublicOnDev: true },
|
||||
},
|
||||
{ uid },
|
||||
);
|
||||
//WHEN
|
||||
await handler(req, res, next);
|
||||
|
||||
//THEN
|
||||
expect(next).toHaveBeenCalledWith();
|
||||
});
|
||||
it("should fail without authentication if publicOnDev on prod ", async () => {
|
||||
//GIVEN
|
||||
const req = givenRequest(
|
||||
{
|
||||
...requireAdminPermission,
|
||||
authenticationOptions: { isPublicOnDev: true },
|
||||
},
|
||||
{ uid },
|
||||
);
|
||||
//WHEN
|
||||
await handler(req, res, next);
|
||||
|
||||
//THEN
|
||||
expect(next).toHaveBeenCalledWith(
|
||||
expect.toMatchMonkeyError(
|
||||
new MonkeyError(403, "You don't have permission to do this."),
|
||||
),
|
||||
);
|
||||
});
|
||||
it("should fail without admin permissions", async () => {
|
||||
//GIVEN
|
||||
const req = givenRequest(requireAdminPermission, { uid });
|
||||
|
||||
//WHEN
|
||||
await handler(req, res, next);
|
||||
|
||||
//THEN
|
||||
expect(next).toHaveBeenCalledWith(
|
||||
expect.toMatchMonkeyError(
|
||||
new MonkeyError(403, "You don't have permission to do this."),
|
||||
),
|
||||
);
|
||||
expect(isAdminMock).toHaveBeenCalledWith(uid);
|
||||
});
|
||||
});
|
||||
describe("user checks", () => {
|
||||
it("should fetch user only once", async () => {
|
||||
//GIVEN
|
||||
const req = givenRequest(
|
||||
{
|
||||
requirePermission: ["canReport", "canManageApeKeys"],
|
||||
},
|
||||
{ uid },
|
||||
);
|
||||
|
||||
//WHEN
|
||||
await handler(req, res, next);
|
||||
|
||||
//THEN
|
||||
expect(getPartialUserMock).toHaveBeenCalledOnce();
|
||||
expect(getPartialUserMock).toHaveBeenCalledWith(
|
||||
uid,
|
||||
"check user permissions",
|
||||
["canReport", "canManageApeKeys"],
|
||||
);
|
||||
});
|
||||
it("should fail if authentication is missing", async () => {
|
||||
//GIVEN
|
||||
const req = givenRequest({
|
||||
requirePermission: ["canReport", "canManageApeKeys"],
|
||||
});
|
||||
|
||||
//WHEN
|
||||
await handler(req, res, next);
|
||||
|
||||
//THEN
|
||||
expect(next).toHaveBeenCalledWith(
|
||||
expect.toMatchMonkeyError(
|
||||
new MonkeyError(
|
||||
403,
|
||||
"Failed to check permissions, authentication required.",
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe("quoteMod check", () => {
|
||||
const requireQuoteMod: EndpointMetadata = {
|
||||
requirePermission: "quoteMod",
|
||||
};
|
||||
|
||||
it("should pass for quoteAdmin", async () => {
|
||||
//GIVEN
|
||||
getPartialUserMock.mockResolvedValue({ quoteMod: true } as any);
|
||||
const req = givenRequest(requireQuoteMod, { uid });
|
||||
|
||||
//WHEN
|
||||
await handler(req, res, next);
|
||||
|
||||
//THEN
|
||||
expect(next).toHaveBeenCalledWith();
|
||||
expect(getPartialUserMock).toHaveBeenCalledWith(
|
||||
uid,
|
||||
"check user permissions",
|
||||
["quoteMod"],
|
||||
);
|
||||
});
|
||||
it("should pass for specific language", async () => {
|
||||
//GIVEN
|
||||
getPartialUserMock.mockResolvedValue({ quoteMod: "english" } as any);
|
||||
const req = givenRequest(requireQuoteMod, { uid });
|
||||
|
||||
//WHEN
|
||||
await handler(req, res, next);
|
||||
|
||||
//THEN
|
||||
expect(next).toHaveBeenCalledWith();
|
||||
expect(getPartialUserMock).toHaveBeenCalledWith(
|
||||
uid,
|
||||
"check user permissions",
|
||||
["quoteMod"],
|
||||
);
|
||||
});
|
||||
it("should fail for empty string", async () => {
|
||||
//GIVEN
|
||||
getPartialUserMock.mockResolvedValue({ quoteMod: "" } as any);
|
||||
const req = givenRequest(requireQuoteMod, { uid });
|
||||
|
||||
//WHEN
|
||||
await handler(req, res, next);
|
||||
|
||||
//THEN
|
||||
expect(next).toHaveBeenCalledWith(
|
||||
expect.toMatchMonkeyError(
|
||||
new MonkeyError(403, "You don't have permission to do this."),
|
||||
),
|
||||
);
|
||||
});
|
||||
it("should fail for missing quoteMod", async () => {
|
||||
//GIVEN
|
||||
getPartialUserMock.mockResolvedValue({} as any);
|
||||
const req = givenRequest(requireQuoteMod, { uid });
|
||||
|
||||
//WHEN
|
||||
await handler(req, res, next);
|
||||
|
||||
//THEN
|
||||
expect(next).toHaveBeenCalledWith(
|
||||
expect.toMatchMonkeyError(
|
||||
new MonkeyError(403, "You don't have permission to do this."),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe("canReport check", () => {
|
||||
const requireCanReport: EndpointMetadata = {
|
||||
requirePermission: "canReport",
|
||||
};
|
||||
|
||||
it("should fail if user cannot report", async () => {
|
||||
//GIVEN
|
||||
getPartialUserMock.mockResolvedValue({ canReport: false } as any);
|
||||
const req = givenRequest(requireCanReport, { uid });
|
||||
|
||||
//WHEN
|
||||
await handler(req, res, next);
|
||||
|
||||
//THEN
|
||||
expect(next).toHaveBeenCalledWith(
|
||||
expect.toMatchMonkeyError(
|
||||
new MonkeyError(403, "You don't have permission to do this."),
|
||||
),
|
||||
);
|
||||
expect(getPartialUserMock).toHaveBeenCalledWith(
|
||||
uid,
|
||||
"check user permissions",
|
||||
["canReport"],
|
||||
);
|
||||
});
|
||||
it("should pass if user can report", async () => {
|
||||
//GIVEN
|
||||
getPartialUserMock.mockResolvedValue({ canReport: true } as any);
|
||||
const req = givenRequest(requireCanReport, { uid });
|
||||
|
||||
//WHEN
|
||||
await handler(req, res, next);
|
||||
|
||||
//THEN
|
||||
expect(next).toHaveBeenCalledWith();
|
||||
});
|
||||
it("should pass if canReport is not set", async () => {
|
||||
//GIVEN
|
||||
getPartialUserMock.mockResolvedValue({} as any);
|
||||
const req = givenRequest(requireCanReport, { uid });
|
||||
|
||||
//WHEN
|
||||
await handler(req, res, next);
|
||||
|
||||
//THEN
|
||||
expect(next).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
describe("canManageApeKeys check", () => {
|
||||
const requireCanReport: EndpointMetadata = {
|
||||
requirePermission: "canManageApeKeys",
|
||||
};
|
||||
|
||||
it("should fail if user cannot report", async () => {
|
||||
//GIVEN
|
||||
getPartialUserMock.mockResolvedValue({ canManageApeKeys: false } as any);
|
||||
const req = givenRequest(requireCanReport, { uid });
|
||||
|
||||
//WHEN
|
||||
await handler(req, res, next);
|
||||
|
||||
//THEN
|
||||
expect(next).toHaveBeenCalledWith(
|
||||
expect.toMatchMonkeyError(
|
||||
new MonkeyError(
|
||||
403,
|
||||
"You have lost access to ape keys, please contact support",
|
||||
),
|
||||
),
|
||||
);
|
||||
expect(getPartialUserMock).toHaveBeenCalledWith(
|
||||
uid,
|
||||
"check user permissions",
|
||||
["canManageApeKeys"],
|
||||
);
|
||||
});
|
||||
it("should pass if user can report", async () => {
|
||||
//GIVEN
|
||||
getPartialUserMock.mockResolvedValue({ canManageApeKeys: true } as any);
|
||||
const req = givenRequest(requireCanReport, { uid });
|
||||
|
||||
//WHEN
|
||||
await handler(req, res, next);
|
||||
|
||||
//THEN
|
||||
expect(next).toHaveBeenCalledWith();
|
||||
});
|
||||
it("should pass if canManageApeKeys is not set", async () => {
|
||||
//GIVEN
|
||||
getPartialUserMock.mockResolvedValue({} as any);
|
||||
const req = givenRequest(requireCanReport, { uid });
|
||||
|
||||
//WHEN
|
||||
await handler(req, res, next);
|
||||
|
||||
//THEN
|
||||
expect(next).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function givenRequest(
|
||||
metadata: EndpointMetadata,
|
||||
decodedToken?: Partial<DecodedToken>,
|
||||
): TsRestRequest {
|
||||
return { tsRestRoute: { metadata }, ctx: { decodedToken } } as any;
|
||||
}
|
||||
Reference in New Issue
Block a user