Skip to content

Commit 97d047f

Browse files
authored
Merge pull request #30 from kamath/ani/cla-34-certain-routes-should-only-be-accessible-by-admin-users
Ani/cla 34 certain routes should only be accessible by admin users
2 parents c00a81d + c6545f8 commit 97d047f

6 files changed

Lines changed: 77 additions & 5 deletions

File tree

‎apps/backend/package.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"start": "bun run src/index.ts",
99
"lint": "eslint . --max-warnings 0 --fix",
1010
"check-types": "tsc --noEmit",
11-
"build": "tsc --noEmit"
11+
"build": "tsc --noEmit"
1212
},
1313
"exports": {
1414
".": "./src/index.ts",
@@ -20,6 +20,7 @@
2020
"@hono/zod-openapi": "^0.18.0",
2121
"@posthog/ai": "^7.2.0",
2222
"@repo/api-types": "*",
23+
"@workos-inc/node": "^7.75.1",
2324
"ai": "^5.0.104",
2425
"hono": "^4.7.0",
2526
"posthog-node": "^5.14.1",

‎apps/backend/src/client-types.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ import type { OpenAPIHono } from "@hono/zod-openapi";
88
// This provides type hints without needing to resolve the actual route implementations
99
export type AppType = OpenAPIHono<{
1010
Bindings: Record<string, unknown>;
11-
Variables: { userId: string };
11+
Variables: { userId: string; isAdmin: boolean };
1212
}>;

‎apps/backend/src/index.ts‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const api = new OpenAPIHono<{
4242
Bindings: Env;
4343
Variables: {
4444
userId: string;
45+
isAdmin: boolean;
4546
db: Database;
4647
};
4748
}>();

‎apps/backend/src/middleware/auth.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createMiddleware } from "hono/factory";
22
import { HTTPException } from "hono/http-exception";
33
import { decryptUserId } from "@/utils/auth";
4+
import { WorkOS } from "@workos-inc/node";
45

56
export const apiKeyAuth = createMiddleware(async (c, next) => {
67
const apiKey = c.req.header("X-API-Key");
@@ -13,8 +14,15 @@ export const apiKeyAuth = createMiddleware(async (c, next) => {
1314

1415
try {
1516
const decryptedUserId = await decryptUserId(apiKey, c.env);
17+
const workos = new WorkOS(c.env.WORKOS_API_KEY);
18+
19+
const user = await workos.userManagement.getUser(decryptedUserId);
20+
21+
console.log("User:", JSON.stringify(user, null, 2));
22+
1623
console.log("Decrypted user ID:", decryptedUserId);
1724
c.set("userId", decryptedUserId);
25+
c.set("isAdmin", user.metadata?.role === "superduperadmin");
1826
} catch (error) {
1927
console.error("Failed to decrypt user ID:", error);
2028
throw new HTTPException(403, {
@@ -24,3 +32,16 @@ export const apiKeyAuth = createMiddleware(async (c, next) => {
2432

2533
await next();
2634
});
35+
36+
export const requireAdmin = createMiddleware(async (c, next) => {
37+
const isAdmin = c.get("isAdmin");
38+
39+
if (!isAdmin) {
40+
throw new HTTPException(403, {
41+
message:
42+
"Admin access required. This endpoint is only accessible to administrators.",
43+
});
44+
}
45+
46+
await next();
47+
});

‎apps/backend/src/routes/problems.ts‎

Lines changed: 52 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { OpenAPIHono } from "@hono/zod-openapi";
22
import type { Context } from "hono";
3+
import { HTTPException } from "hono/http-exception";
34
import {
45
generateProblemText,
56
getProblemText,
@@ -67,6 +68,7 @@ const problems = new OpenAPIHono<{
6768
Bindings: Env;
6869
Variables: {
6970
userId: string;
71+
isAdmin: boolean;
7072
db: Database;
7173
};
7274
}>();
@@ -123,7 +125,10 @@ async function shouldReturnIdempotentError(
123125

124126
// Helper to start workflow if autoGenerate is enabled (for problem creation)
125127
async function startWorkflowIfAuto(
126-
c: Context<{ Bindings: Env; Variables: { userId: string; db: Database } }>,
128+
c: Context<{
129+
Bindings: Env;
130+
Variables: { userId: string; isAdmin: boolean; db: Database };
131+
}>,
127132
problemId: string,
128133
model?: string,
129134
autoGenerate: boolean = true,
@@ -158,7 +163,10 @@ async function startWorkflowIfAuto(
158163

159164
// Helper to start workflow from a specific step if enqueueNextStep is enabled
160165
async function startWorkflowFromStepIfEnabled(
161-
c: Context<{ Bindings: Env; Variables: { userId: string; db: Database } }>,
166+
c: Context<{
167+
Bindings: Env;
168+
Variables: { userId: string; isAdmin: boolean; db: Database };
169+
}>,
162170
problemId: string,
163171
currentStep: GenerationStep,
164172
model?: string,
@@ -718,6 +726,15 @@ problems.openapi(generateInputCodeRoute, async (c) => {
718726
problems.openapi(getInputCodeRoute, async (c) => {
719727
const db = c.get("db");
720728
const { problemId } = c.req.valid("param");
729+
const isAdmin = c.get("isAdmin");
730+
731+
// Check admin access
732+
if (!isAdmin) {
733+
throw new HTTPException(403, {
734+
message:
735+
"Admin access required. This endpoint is only accessible to administrators.",
736+
});
737+
}
721738

722739
// Validate prerequisite: test cases must exist
723740
const problem = await getProblem(problemId, db);
@@ -814,6 +831,7 @@ problems.openapi(generateInputsRoute, async (c) => {
814831
problems.openapi(getInputsRoute, async (c) => {
815832
const db = c.get("db");
816833
const { problemId } = c.req.valid("param");
834+
const isAdmin = c.get("isAdmin");
817835

818836
// Validate prerequisite: all test cases must have inputCode
819837
const problem = await getProblem(problemId, db);
@@ -835,7 +853,20 @@ problems.openapi(getInputsRoute, async (c) => {
835853
);
836854
}
837855

838-
const result = await getTestCaseInputs(problemId, db);
856+
const allInputs = await getTestCaseInputs(problemId, db);
857+
858+
// If admin, return all inputs; if not admin, only return inputs for sample test cases
859+
let result: unknown[];
860+
if (isAdmin) {
861+
result = allInputs;
862+
} else {
863+
// Filter to only include inputs for sample test cases
864+
result = problem.testCases
865+
.filter((tc) => tc.isSampleCase)
866+
.map((tc) => tc.input)
867+
.filter((input) => input !== null && input !== undefined);
868+
}
869+
839870
return c.json({ success: true as const, data: result }, 200);
840871
});
841872

@@ -947,6 +978,15 @@ problems.openapi(generateSolutionRoute, async (c) => {
947978
problems.openapi(getSolutionRoute, async (c) => {
948979
const db = c.get("db");
949980
const { problemId } = c.req.valid("param");
981+
const isAdmin = c.get("isAdmin");
982+
983+
// Check admin access
984+
if (!isAdmin) {
985+
throw new HTTPException(403, {
986+
message:
987+
"Admin access required. This endpoint is only accessible to administrators.",
988+
});
989+
}
950990

951991
// Validate prerequisite: all test cases must have input field populated
952992
const problem = await getProblem(problemId, db);
@@ -1085,6 +1125,15 @@ problems.openapi(generateOutputsRoute, async (c) => {
10851125
problems.openapi(getOutputsRoute, async (c) => {
10861126
const db = c.get("db");
10871127
const { problemId } = c.req.valid("param");
1128+
const isAdmin = c.get("isAdmin");
1129+
1130+
// Check admin access
1131+
if (!isAdmin) {
1132+
throw new HTTPException(403, {
1133+
message:
1134+
"Admin access required. This endpoint is only accessible to administrators.",
1135+
});
1136+
}
10881137

10891138
// Validate prerequisite: solution must exist
10901139
const problem = await getProblem(problemId, db);

‎bun.lockb‎

544 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)