From 05e65ade650d14275799eb09e64e349111767e29 Mon Sep 17 00:00:00 2001 From: Kevin Yu Date: Sat, 9 Nov 2024 00:24:04 -0500 Subject: [PATCH 1/2] Experiment with non-standard properties, prereqs --- app/challenges/page.tsx | 39 +++++++++++++++++++++++++++++++++------ util/admin.ts | 8 ++++---- util/challenges.ts | 2 ++ 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/app/challenges/page.tsx b/app/challenges/page.tsx index ecbcd44..e038e03 100644 --- a/app/challenges/page.tsx +++ b/app/challenges/page.tsx @@ -11,6 +11,7 @@ import CTFNotStarted from '@/components/CTFNotStarted'; // Utils import { getChallenges } from '@/util/challenges'; import { getMyProfile } from '@/util/profile'; +import { getAdminChallenges } from '@/util/admin'; import { AUTH_COOKIE_NAME } from '@/util/config'; @@ -27,20 +28,46 @@ export default async function ChallengesPage() { if (profile.kind === 'badToken') return redirect('/logout'); - return challenges.kind === 'goodChallenges' ? ( + if (challenges.kind !== 'goodChallenges') return ( + + ); + + // Support non-standard properties by sourcing them from the admin endpoint. + const adminData = await getAdminChallData(); + let challs = challenges.data; + + if (adminData) { + // Filter out challs with prereqs that are not met yet + const solved = new Set(profile.data.solves.map((c) => c.id)); + challs = challs.filter((c) => !adminData[c.id].prereqs || adminData[c.id].prereqs!.every((p) => solved.has(p))); + + // Inject desired properties back into client challenges + for (const c of challs) { + c.difficulty = adminData[c.id].difficulty; + } + } + + return (
- ) : ( - - ) + ); +} + +async function getAdminChallData() { + if (!process.env.ADMIN_TOKEN) return; + + const res = await getAdminChallenges(process.env.ADMIN_TOKEN); + if (res.kind === 'badToken') return; + + return Object.fromEntries(res.data.map((c) => [c.id, c])); } diff --git a/util/admin.ts b/util/admin.ts index 2c8e2de..57b778b 100644 --- a/util/admin.ts +++ b/util/admin.ts @@ -2,9 +2,11 @@ import type { Challenge } from '@/util/challenges'; import type { BadTokenResponse } from '@/util/errors'; -export type AdminChallenge = Challenge & { +export type AdminChallenge = Exclude & { flag: string, points: { min: number, max: number } + + prereqs?: string[], // Non-standard } type AdminChallengesResponse = { @@ -15,9 +17,7 @@ type AdminChallengesResponse = { export async function getAdminChallenges(token: string): Promise { const res = await fetch(`${process.env.API_BASE}/admin/challs`, { - headers: { - 'Authorization': `Bearer ${token}` - } + headers: { 'Authorization': `Bearer ${token}` } }); return res.json(); diff --git a/util/challenges.ts b/util/challenges.ts index a8bf331..7816ede 100644 --- a/util/challenges.ts +++ b/util/challenges.ts @@ -11,6 +11,8 @@ export type Challenge = { sortWeight: number, solves: number, points: number, + + difficulty?: string, // Non-standard } type FileData = { From fd7687e94772b772811f079d6e520dc7f238eaa7 Mon Sep 17 00:00:00 2001 From: Kevin Yu Date: Sat, 9 Nov 2024 00:44:35 -0500 Subject: [PATCH 2/2] Add wrapping for challenge names --- app/challenges/Challenge.tsx | 2 +- app/challenges/GridChallenge.tsx | 2 +- app/challenges/GridChallengeModal.tsx | 2 +- app/challenges/SolvesModal.tsx | 4 ++-- app/profile/ProfileSolve.tsx | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/challenges/Challenge.tsx b/app/challenges/Challenge.tsx index 1e55a62..f549ef0 100644 --- a/app/challenges/Challenge.tsx +++ b/app/challenges/Challenge.tsx @@ -18,7 +18,7 @@ export default function Challenge(props: Challenge & { solved: boolean }) { return (
-

+

{props.category}/{props.name}

diff --git a/app/challenges/GridChallenge.tsx b/app/challenges/GridChallenge.tsx index 07d9d99..8066814 100644 --- a/app/challenges/GridChallenge.tsx +++ b/app/challenges/GridChallenge.tsx @@ -15,7 +15,7 @@ export default function GridChallenge(props: Challenge & { solved: boolean }) { className={'px-8 py-6 rounded border transition duration-150 text-center focus:outline-none focus:ring-2 backdrop-blur-sm ' + (props.solved ? 'bg-success/20 border-success hover:bg-success/30' : 'bg-black/50 border-tertiary hover:border-secondary')} onClick={() => setOpen(true)} > -

+

{props.solved && ( )} diff --git a/app/challenges/GridChallengeModal.tsx b/app/challenges/GridChallengeModal.tsx index f10e6c7..7a56ce1 100644 --- a/app/challenges/GridChallengeModal.tsx +++ b/app/challenges/GridChallengeModal.tsx @@ -37,7 +37,7 @@ export default function GridChallengeModal(props: GridChallengeModalProps) { -

+

{props.challenge.name}

diff --git a/app/challenges/SolvesModal.tsx b/app/challenges/SolvesModal.tsx index a5d2c45..4c94394 100644 --- a/app/challenges/SolvesModal.tsx +++ b/app/challenges/SolvesModal.tsx @@ -19,10 +19,10 @@ export default function SolvesModal(props: SolvesModalProps) { isOpen={props.open} setIsOpen={props.setOpen} > -

+

Solves for {props.challenge.name} - + {props.challenge.category}

diff --git a/app/profile/ProfileSolve.tsx b/app/profile/ProfileSolve.tsx index 129c74e..dee44af 100644 --- a/app/profile/ProfileSolve.tsx +++ b/app/profile/ProfileSolve.tsx @@ -5,7 +5,7 @@ import { DateTime } from 'luxon'; export default function ProfileSolve(props: Solve) { return (
-
+
{props.name} {props.category}