From 65661277160f1b5b9aff339b849b514cce67292e Mon Sep 17 00:00:00 2001 From: Pranav Ramesh Date: Sat, 10 May 2025 22:38:44 -0400 Subject: [PATCH 1/2] feat: update careers page --- src/pages/careers.jsx | 223 +++++++++++++++++++++--------------------- 1 file changed, 114 insertions(+), 109 deletions(-) diff --git a/src/pages/careers.jsx b/src/pages/careers.jsx index 45751a9f..e279f7b3 100644 --- a/src/pages/careers.jsx +++ b/src/pages/careers.jsx @@ -32,7 +32,7 @@ export default function Careers() { }, { personName: 'Jiaming Wang', - position: 'CTO', + position: 'Software Engineer', image: '../mish.jpg', width: '200', height: '200', @@ -48,25 +48,15 @@ export default function Careers() { bio: "Mishael Adelanwa, a current Bunton-Waller fellow at Penn State University and an innovative investor with an eye for emerging technologies, invested in CTFGuide in a Pre Seed Round in January 2023. Adept at identifying and supporting promising ventures, Mishael brings expertise and curiosity to the world of blockchain technology and cybersecurity." }, - { - personName: 'Kshitij Kochhar', + personName: 'David Youm', position: 'Software Engineer', - image: '../kkochhar.jpg', - width: '200', - height: '200', - bio: "Kshitij is a current Computer Science student at the University of Maryland who specializes in full-stack development. Outside of coding he enjoys going to the Gym, playing Tennis, and hanging out with friends." - }, - - { - personName: 'Almond Milk', - position: 'Content', image: '../mish.jpg', width: '200', height: '200', bio: "Almond Force is a team dedicated to providing the cyber security and IT community with training on different platforms and events such as CTFs, Hack the Box, TryHackMe, and more! The team's founder is Almond Milk, and we strive to grow enough to where we can release content as our full-time career." }, - + ]; @@ -121,13 +111,13 @@ export default function Careers() {

- careers@ + About - CTFGuide +  CTFGuide

- Join the company that's building the modern cybersecurity upskilling platform. + The company that's building the modern cybersecurity upskilling platform.

@@ -135,116 +125,131 @@ export default function Careers() {
-

Working with us

-

- We're all about open, collaborative, and inclusive culture. We believe in learning from each other and the power of community. -



- Our team? Learners, not know-it-alls. We're after people who want to grow, not those who think they've got it all figured out. -



- We're remote-friendly, but a lot of us are in State College, PA. Work where you work best - remote, in-office, or a mix of both. - -

- -

Backed by the best

-

CTFGuide is funded by the best of the best. We're backed by investors like Penn State University and Bullmont Capital.

- -

Open Positions

-

- Applications require you to login with your Google account. You can also email your resume to staff@ctfguide.com. -

-
-
-

Frontend SWE

-

Remote

+

Our Team

+
+ {team.map((member, index) => ( +
+
+

{member.personName}

+

{member.position}

+
+
+ ))}
-
- Apply +

Working with us

+

+ We're all about open, collaborative, and inclusive culture. We believe in learning from each other and the power of community. +

+ Our team? Learners, not know-it-alls. We're after people who want to grow, not those who think they've got it all figured out. +

+ +

Support and community

+

+ CTFGuide is supported by our university, generous community contributors, and early believers who see the value in making cybersecurity accessible. +

+ +

Open Positions

+

+ Applications require you to login with your Google account. You can also email your resume to staff@ctfguide.com. +

+ +
+ {listings.map((listing, index) => ( +
+
+

{listing.roleName}

+
+ {listing.team} + + {listing.position} + + {listing.type} +
+
+ + Apply + +
+ ))}
-
+
-
-
-

Content Writer

-

Remote

-
+
+
+

Sponsors & Partners

-
- Apply -
-
+
+
+
+

STiBaRC

+ Compute +
+

+ STiBaRC is a social media platform created by Joshua Herron, initially started as a joke and a gift to a friend. It has since expanded to include features like a messenger, livestreaming service, record label, and ad system . + +

+ https://stibarc.com +
-
-
-

Infrastructure Engineer

-

Remote

-
-
- Apply -
-
-
+
-
-
-

Our Team

-
-
-

Pranav Ramesh

-

Founder, CEO

-
-
-

Abhi Byreddy

-

Co-Founder, COO

-
-
-

Jiaming Wang

-

CTO

-
+
+
+
+

PSU Web Dev Club

+ Compute +
+

+ The Penn State Web Dev Club is a student-run organization at Penn State University that focuses on building a community for web development enthusiasts.

-
-

Mish Adelanwa

-

Advisor

-
+ + https://psuwebdev.org/ +
-
-

Stephen Stefantos

-

SWE

-
- -
-

Kshitij Kochhar

-

SWE

-
+ +
-
-

David Youm

-

SWE

-
+
+
+
+

Penn State University

+ Capital +
+

+ Penn State University is a public research university in State College, Pennsylvania. It is the flagship institution of the Pennsylvania State System of Higher Education. +

-
-

Josh Herron

-

Infrastructure

-
+ https://www.psu.edu/ +
+
-
-

Ben Haulk

-

SWE Intern

-
+
+
+
+

Bullmont Capital

+ Capital +
+

+ Bullmont Capital is a venture capital firm that invests in early-stage startups. +

-
-

Travis Peck

-

SWE Intern

-
+ https://bullmontcapital.com/ +
+
-
-

Arno Raath

-

SWE Intern

-
-
+ + + + + +
From f09b11166e8f44e361897b212d1cd823f5ed4e53 Mon Sep 17 00:00:00 2001 From: Pranav Ramesh Date: Sat, 12 Jul 2025 11:12:10 -0400 Subject: [PATCH 2/2] feat: Allow unauthenticated users to view challenge pages with limited access MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major Features: - Allow unauthenticated users to view specific challenge pages (UUID format) - Show all tabs with lock icons for restricted features instead of hiding - Implement locked content components with feature-specific call-to-action screens - Add public API endpoint for challenge data without authentication - Implement smooth points counting animation in navbar when challenges solved UI/UX Improvements: - Replace terminal access gradients with sleek, on-brand styling - Fix cramped tabs on unauthenticated view with proper spacing - Remove rounded edges from login/signup buttons and navigation - Update copyright year from 2024 to 2025 in auth footer - Change terminal access text to more conversational tone Technical Implementation: - Add authentication loading state to prevent UI flashing - Create useCountingAnimation hook for navbar points animation - Update middleware to allow unauthenticated access to challenge URLs - Fix request utility redirect logic for challenge pages - Modify _app.jsx to check for tokens before making authenticated API calls Backend Integration: - New public API route: GET /public/challenges/:id - Returns only public challenge fields (no solutions/hints) - Seamless fallback between authenticated and public endpoints 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/components/StandardNav.jsx | 18 +- src/components/auth/AuthFooter.jsx | 2 +- src/middleware.js | 6 + src/pages/_app.jsx | 18 + src/pages/challenges/[...id].jsx | 692 ++++++++++++++++++++++------- src/pages/create.jsx | 2 +- src/utils/request.js | 26 +- 7 files changed, 602 insertions(+), 162 deletions(-) diff --git a/src/components/StandardNav.jsx b/src/components/StandardNav.jsx index 0df2b72c..28d3007c 100644 --- a/src/components/StandardNav.jsx +++ b/src/components/StandardNav.jsx @@ -57,8 +57,14 @@ const DEFAULT_NOTIFICATION = { receivedTime: '', }; -export function StandardNav({ guestAllowed, alignCenter = true }) { +export function StandardNav({ guestAllowed, alignCenter = true, animatedPoints, isPointsAnimating: propIsPointsAnimating }) { const { role, points } = useContext(Context); + + // Use animated points if provided, otherwise use regular points + const displayPoints = animatedPoints !== undefined ? animatedPoints : points; + + // Use prop animation state if provided, otherwise track locally + const isPointsAnimating = propIsPointsAnimating !== undefined ? propIsPointsAnimating : false; const [terminaIsOpen, setTerminalIsOpen] = useState(false); const [upgradeModalOpen, setUpgradeModalOpen] = useState(false); @@ -469,11 +475,15 @@ export function StandardNav({ guestAllowed, alignCenter = true }) { )}
-

- {points} +

+ {displayPoints}

diff --git a/src/components/auth/AuthFooter.jsx b/src/components/auth/AuthFooter.jsx index 8bf0dc8c..ca5cdf62 100644 --- a/src/components/auth/AuthFooter.jsx +++ b/src/components/auth/AuthFooter.jsx @@ -4,7 +4,7 @@ export default function AuthFooter() { return (
- © CTFGuide Corporation 2024
+ © CTFGuide Corporation 2025
Terms of Service Privacy Policy diff --git a/src/middleware.js b/src/middleware.js index a3d69092..39cdd83c 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -33,6 +33,12 @@ export function middleware(req) { return NextResponse.next(); } + // Allow access to specific challenge pages (UUID format) + const challengePathRegex = /^\/challenges\/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/; + if (challengePathRegex.test(pathname)) { + return NextResponse.next(); + } + const idToken = req.cookies.get('idToken'); // Redirect to login if token is missing or invalid diff --git a/src/pages/_app.jsx b/src/pages/_app.jsx index cc9bae2b..cfe16a45 100644 --- a/src/pages/_app.jsx +++ b/src/pages/_app.jsx @@ -28,6 +28,24 @@ export default function App({ Component, pageProps }) { }; const fetchUser = async () => { + // Check if user has a token before making the request + const getCookie = () => { + try { + const value = `; ${document.cookie}`; + const parts = value.split(`; idToken=`); + if (parts.length === 2) return parts.pop().split(';').shift(); + } catch (error) { + return ""; + } + return ""; + }; + + const token = getCookie(); + if (!token) { + // No token, user is not authenticated - don't make the API call + return; + } + const url = process.env.NEXT_PUBLIC_API_URL + "/account"; const user = await request(url, "GET", null); diff --git a/src/pages/challenges/[...id].jsx b/src/pages/challenges/[...id].jsx index 6cd52aa1..621573a9 100644 --- a/src/pages/challenges/[...id].jsx +++ b/src/pages/challenges/[...id].jsx @@ -1,5 +1,6 @@ import { MarkdownViewer } from "@/components/MarkdownViewer"; import { StandardNav } from "@/components/StandardNav"; +import { Logo } from "@/components/Logo"; import request from "@/utils/request"; import { Dialog } from "@headlessui/react"; import { DocumentTextIcon } from "@heroicons/react/20/solid"; @@ -24,6 +25,164 @@ import WriteupModal from '@/components/WriteupModal'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend } from 'recharts'; import { useSearchParams } from 'next/navigation'; +// Locked content component for restricted tabs +function LockedContent({ tabName }) { + const features = { + 'Hints': { + icon: 'fas fa-question', + title: 'Progressive Hints System', + description: 'Get step-by-step guidance when you\'re stuck', + benefits: ['Smart hint progression', 'Learn at your own pace', 'Detailed explanations'] + }, + 'Comments': { + icon: 'fas fa-comments', + title: 'Community Discussions', + description: 'Connect with other learners and share insights', + benefits: ['Ask questions', 'Share discoveries', 'Learn from others'] + }, + 'Writeups': { + icon: 'fas fa-book', + title: 'Solution Writeups', + description: 'Detailed explanations of how to solve challenges', + benefits: ['Step-by-step solutions', 'Multiple approaches', 'Learn new techniques'] + }, + 'AI': { + icon: 'fas fa-robot', + title: 'AI Assistant', + description: 'Get personalized help from our AI tutor', + benefits: ['Instant guidance', 'Tailored explanations', 'Available 24/7'] + } + }; + + const feature = features[tabName] || features['Hints']; + + return ( +
+
+
+ +
+

+ {feature.title} +

+

+ {feature.description} +

+
+ {feature.benefits.map((benefit, index) => ( +
+ + {benefit} +
+ ))} +
+
+ + Login to Access + + + Create Free Account + +
+
+
+ ); +} + +// Simple public navigation component for unauthenticated users +function PublicNav() { + return ( + + ); +} + +// Utility function to check if user is authenticated +function useAuthentication() { + const { username } = useContext(Context); + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [authLoading, setAuthLoading] = useState(true); + + useEffect(() => { + const token = getCookie('idToken'); + setIsAuthenticated(!!token && !!username); + + // Set loading to false after checking authentication + // Small delay to prevent flash of unauthenticated content + setTimeout(() => { + setAuthLoading(false); + }, 100); + }, [username]); + + return { isAuthenticated, authLoading }; +} + +// Counting animation hook for points +function useCountingAnimation(targetValue, duration = 1500) { + const [count, setCount] = useState(targetValue || 0); + const [isAnimating, setIsAnimating] = useState(false); + + const startAnimation = (newTarget) => { + if (isAnimating) return; + + setIsAnimating(true); + const startValue = count; + const startTime = Date.now(); + + const animate = () => { + const now = Date.now(); + const elapsed = now - startTime; + const progress = Math.min(elapsed / duration, 1); + + // Easing function for smooth animation + const easeOutQuart = 1 - Math.pow(1 - progress, 4); + const currentCount = Math.floor(startValue + (newTarget - startValue) * easeOutQuart); + + setCount(currentCount); + + if (progress < 1) { + requestAnimationFrame(animate); + } else { + setCount(newTarget); + setIsAnimating(false); + } + }; + + requestAnimationFrame(animate); + }; + + return { count, startAnimation, isAnimating }; +} + // Move styles to a separate useEffect function useHighlightStyles() { useEffect(() => { @@ -140,6 +299,20 @@ export default function Challenge() { const [selectedWriteup, setSelectedWriteup] = useState(null); const [isTerminalFullscreen, setIsTerminalFullscreen] = useState(false); const [isChallengeFullscreen, setIsChallengeFullscreen] = useState(true); + const { isAuthenticated, authLoading } = useAuthentication(); + + // Context for navbar points update + const { points, setPoints } = useContext(Context); + + // Points animation for navbar + const navbarPointsAnimation = useCountingAnimation(points, 1500); + + // Initialize animation with current points + useEffect(() => { + if (points > 0 && navbarPointsAnimation.count !== points) { + navbarPointsAnimation.startAnimation(points); + } + }, [points]); // I hate this const [urlChallengeId, urlSelectedTab, urlWriteupId] = (router ?? {})?.query?.id ?? [undefined, undefined, undefined]; @@ -158,16 +331,14 @@ export default function Challenge() { // Tab system is designed to keep browser state in url, // while mainting persistence of the terminal. const tabs = { - 'description': { text: 'Description', element: DescriptionPage, }, - 'hints': { text: 'Hints', element: HintsPage, }, - - 'comments': { text: 'Comments', element: CommentsPage, }, - - 'write-up': { text: 'Writeups', element: WriteUpPage, }, - 'leaderboard': { text: 'Leaderboard', element: LeaderboardPage, }, - 'AI': { text: 'AI', element: AIPage, }, - + 'description': { text: 'Description', element: DescriptionPage, requiresAuth: false }, + 'hints': { text: 'Hints', element: HintsPage, requiresAuth: true }, + 'comments': { text: 'Comments', element: CommentsPage, requiresAuth: true }, + 'write-up': { text: 'Writeups', element: WriteUpPage, requiresAuth: true }, + 'leaderboard': { text: 'Leaderboard', element: LeaderboardPage, requiresAuth: false }, + 'AI': { text: 'AI', element: AIPage, requiresAuth: true }, } + // Allow selection of restricted tabs for unauthenticated users (will show locked content) const selectedTab = tabs[urlSelectedTab] ?? tabs.description; useEffect(() => { @@ -180,21 +351,77 @@ export default function Challenge() { } try { const getChallengeByIdEndPoint = `${process.env.NEXT_PUBLIC_API_URL}/challenges/${urlChallengeId}`; - const getChallengeResult = await request(getChallengeByIdEndPoint, "GET", null); - if (getChallengeResult.success) { - setCache("challenge", getChallengeResult.body); + + if (isAuthenticated) { + // For authenticated users, use the normal authenticated endpoint + const getChallengeResult = await request(getChallengeByIdEndPoint, "GET", null); + if (getChallengeResult.success) { + setCache("challenge", getChallengeResult.body); + } + } else { + // For unauthenticated users, use the public endpoint + const publicEndpoint = `${process.env.NEXT_PUBLIC_API_URL}/public/challenges/${urlChallengeId}`; + + const response = await fetch(publicEndpoint, { + method: 'GET', + headers: { 'Content-Type': 'application/json' }, + }); + + if (response.ok) { + const result = await response.json(); + + if (result.success && result.challenge) { + setCache("challenge", result.challenge); + } + } else { + + // Fallback to placeholder if the public endpoint fails + const placeholderChallenge = { + id: urlChallengeId, + title: "Challenge Preview", + content: ` +## Access Required + +This challenge requires you to **login to CTFGuide** to view the full content, access hints, submit flags, and track your progress. + +### What you'll get with a free account: +- 🎯 Full challenge descriptions and content +- 💡 Progressive hints system +- 🖥️ Interactive Linux terminals +- 📊 Progress tracking and leaderboards +- 💬 Community discussions and writeups +- 🏆 Points and achievement system + +**Ready to start your cybersecurity journey?** + `, + difficulty: "PREVIEW", + category: ["Preview"], + creator: 'CTFGuide', + views: 0, + upvotes: 0, + downvotes: 0, + }; + setCache("challenge", placeholderChallenge); + } } - } catch (error) { throw "Failed to fetch challenge: " + error; } + } catch (error) { + console.error("Failed to fetch challenge: " + error); + } })(); - }, [urlChallengeId]); + }, [urlChallengeId, isAuthenticated]); const [loadingFlagSubmit, setLoadingFlagSubmit] = useState(false); const [isPointsModalOpen, setIsPointsModalOpen] = useState(false); const [awardedPoints, setAwardedPoints] = useState(0); - const showPointsModal = (points) => { - setAwardedPoints(points); + const showPointsModal = (newPoints) => { + setAwardedPoints(newPoints); setIsPointsModalOpen(true); + + // Update navbar points with animation + const newTotalPoints = points + newPoints; + setPoints(newTotalPoints); + navbarPointsAnimation.startAnimation(newTotalPoints); }; const onSubmitFlag = (e) => { @@ -470,6 +697,37 @@ export default function Challenge() { } }; + + // Show loading state while determining authentication + if (authLoading) { + return ( + <> + + Challenge - CTFGuide + + + +
+ +
+
+
+ +
+

Loading challenge...

+
+
+
+ + ); + } + return ( <> @@ -487,23 +745,34 @@ export default function Challenge() {
- + {isAuthenticated ? ( + + ) : ( + + )}
{isChallengeFullscreen && (
-
+
{Object.entries(tabs).map(([url, tab]) => ( ))}
{selectedWriteup ? ( setSelectedWriteup(null)} writeup={selectedWriteup} /> + ) : selectedTab.requiresAuth && !isAuthenticated ? ( + ) : ( )}
-
-
- - -
-
+ {isAuthenticated ? ( +
+
+ + +
+
+ ) : ( +
+
+ + + Login to CTFGuide + to submit flags and track your progress +
+
+ )} - -

- Password: {password} - -

-

- Remaining Time: {formatTime(minutesRemaining)} - window.open(terminalUrl, '_blank')} className="cursor-pointer hover:text-yellow-500 ml-2 fas fa-expand text-white"> - {showMessage && ( - - Sometimes browsers block iframes, try opening the terminal in full screen if it the terminal is empty. - - - )} -

-
- )} - {fetchingTerminal ? ( -
-
-

- {loadingMessage} -

If you see a black screen, please wait a few seconds and refresh the page.

- -
-
- -
-
+ +

+ Password: {password} + +

+

+ Remaining Time: {formatTime(minutesRemaining)} + window.open(terminalUrl, '_blank')} className="cursor-pointer hover:text-yellow-500 ml-2 fas fa-expand text-white"> + {showMessage && ( + + Sometimes browsers block iframes, try opening the terminal in full screen if it the terminal is empty. + + + )} +

+
+ )} + {fetchingTerminal ? ( +
+
+

+ {loadingMessage} +

If you see a black screen, please wait a few seconds and refresh the page.

+
+
+ ) : ( + isTerminalBooted ? ( +