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/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/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}
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 = {