339 lines
9.3 KiB
TypeScript
339 lines
9.3 KiB
TypeScript
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;
|
|
}
|