-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds test that ensures all api routes are protected (#399)
* chore: adding test:debug for ava debugging + a route guard test that ensures all endpoints are auth guarded. * feat: exclusion list for the auth guard test so public routes can be added * feat: reformat, got eslint and prettier working correctly * fix: comitted by mistake * fix: can be used for health check pings * feat: added more comments * fix: simplify the call * fix: put comment in test * fix: void 0 out, undefined in
- Loading branch information
1 parent
6f1c66d
commit 7ac442c
Showing
4 changed files
with
94 additions
and
0 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
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,7 @@ | ||
import { publicProcedure, router } from "../trpc"; | ||
|
||
export const publicRouter = router({ | ||
healthCheck: publicProcedure.query(() => { | ||
return "Ok"; | ||
}), | ||
}); |
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,84 @@ | ||
import test from "ava"; | ||
import { trpcRouter } from "@/backend/routers/_app"; | ||
import { createContext } from "@/backend/context"; | ||
import type { NextApiRequest, NextApiResponse } from "next"; | ||
import { TRPCError } from "@trpc/server"; | ||
|
||
test("All API endpoints are auth guarded", async (t) => { | ||
/** | ||
* This test verifies that all API endpoints in the application are properly protected by authentication. | ||
* | ||
* It works by: | ||
* 1. Creating a mock request and response object. | ||
* 2. Generating a context with no authentication. | ||
* 3. Creating a trpc caller with this unauthenticated context. | ||
* 4. Iterating through all procedures in the trpcRouter. | ||
* 5. Attempting to call each procedure without authentication. | ||
* 6. Expecting an UNAUTHORIZED error for each call (except for explicitly excluded endpoints). | ||
* | ||
* This ensures that no sensitive endpoints are accidentally left unprotected, | ||
* maintaining the security of the API. | ||
*/ | ||
|
||
// create a mock request object for our calls. purpose of this is to have no auth | ||
const mockReq = { | ||
headers: {}, | ||
cookies: {}, | ||
} as unknown as NextApiRequest; | ||
|
||
// create a mock response object that will be passed to the context | ||
const mockRes = { | ||
getHeader: () => undefined, | ||
setCookie: () => undefined, | ||
setHeader: () => undefined, | ||
} as unknown as NextApiResponse; | ||
|
||
// Create a mock context with no authentication | ||
const ctx = await createContext({ | ||
req: mockReq, | ||
res: mockRes, | ||
}); | ||
|
||
// Define an exclude list for certain router/procedures. these routes are public | ||
// routes, so we don't want to test for auth on them. | ||
const excludeList = ["public.healthCheck"]; | ||
|
||
// Create a caller with this context | ||
const caller = trpcRouter.createCaller(ctx); | ||
|
||
// Get all procedure names | ||
const procedureNames = Object.keys(trpcRouter._def.procedures); | ||
|
||
for (const fullProcedureName of procedureNames) { | ||
if (excludeList.includes(fullProcedureName)) { | ||
continue; | ||
} | ||
// pull apart the router name and procedure name | ||
const [routerName, procedureName] = fullProcedureName.split("."); | ||
try { | ||
// call the procedure without any arguments | ||
await ( | ||
caller[routerName as keyof typeof caller] as Record< | ||
string, | ||
() => Promise<unknown> | ||
> | ||
)[procedureName](); | ||
// if we get here, the procedure was not auth guarded | ||
t.fail(`${fullProcedureName} is not auth guarded`); | ||
} catch (error: unknown) { | ||
// if we get here, the procedure was auth guarded, make sure we get UNAUTHORIZED | ||
if (error instanceof TRPCError && error.code === "UNAUTHORIZED") { | ||
t.pass(`${fullProcedureName} is auth guarded`); | ||
} else { | ||
// if we get here, the procedure was auth guarded, but we got an unexpected error | ||
// since the auth guard fires first, this should never happen | ||
console.error(`Unexpected error for ${fullProcedureName}:`, error); | ||
t.fail( | ||
`${fullProcedureName} threw an unexpected error: ${ | ||
(error as Error).message | ||
}` | ||
); | ||
} | ||
} | ||
} | ||
}); |
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