-
-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
370 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,326 @@ | ||
import { Response } from "express"; | ||
import { checkRequiredPermission } from "../../src/middlewares/permission"; | ||
import { EndpointMetadata } from "@monkeytype/contracts/schemas/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"; | ||
|
||
const uid = "123456789"; | ||
|
||
describe("permission middleware", () => { | ||
const handler = checkRequiredPermission(); | ||
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.mockReset(); | ||
getPartialUserMock.mockReset().mockResolvedValue({} as any); | ||
isDevMock.mockReset().mockReturnValue(false); | ||
isAdminMock.mockReset().mockResolvedValue(false); | ||
}); | ||
|
||
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); | ||
|
||
//THEN | ||
expect(next).toHaveBeenCalledOnce(); | ||
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).toHaveBeenCalledOnce(); | ||
expect(next).toHaveBeenCalledWith( | ||
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).toHaveBeenCalledOnce(); | ||
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).toHaveBeenCalledOnce(); | ||
expect(next).toHaveBeenCalledWith( | ||
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).toHaveBeenCalledOnce(); | ||
expect(next).toHaveBeenCalledWith( | ||
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).toHaveBeenCalledOnce(); | ||
expect(next).toHaveBeenCalledWith( | ||
new MonkeyError(403, "Authentication missing.") | ||
); | ||
}); | ||
}); | ||
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).toHaveBeenCalledOnce(); | ||
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).toHaveBeenCalledOnce(); | ||
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).toHaveBeenCalledOnce(); | ||
expect(next).toHaveBeenCalledWith( | ||
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).toHaveBeenCalledOnce(); | ||
expect(next).toHaveBeenCalledWith( | ||
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).toHaveBeenCalledOnce(); | ||
expect(next).toHaveBeenCalledWith( | ||
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).toHaveBeenCalledOnce(); | ||
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).toHaveBeenCalledOnce(); | ||
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).toHaveBeenCalledOnce(); | ||
expect(next).toHaveBeenCalledWith( | ||
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).toHaveBeenCalledOnce(); | ||
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).toHaveBeenCalledOnce(); | ||
expect(next).toHaveBeenCalledWith(); | ||
}); | ||
}); | ||
}); | ||
|
||
function givenRequest( | ||
metadata: EndpointMetadata, | ||
decodedToken?: Partial<MonkeyTypes.DecodedToken> | ||
): TsRestRequest { | ||
return { tsRestRoute: { metadata }, ctx: { decodedToken } } as any; | ||
} |
Oops, something went wrong.