Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
66b22ac
fix: activities conflict
DuttaNeel07 Jun 14, 2026
f0731a6
fix: dockerfile conflict
DuttaNeel07 Jun 14, 2026
f2304e1
Domains Addition and Activites card Update (#521)
yugeshweb May 22, 2026
1985cd4
add pbctf components
DuttaNeel07 May 25, 2026
15d6381
fix: build issues
DuttaNeel07 May 25, 2026
657ce91
feat: add pbctf page to navbar
DuttaNeel07 May 26, 2026
6a670f9
Remove age
mkumar1205-beep May 26, 2026
0330120
Changes
mkumar1205-beep May 26, 2026
603122f
fix: recaptcha error
DuttaNeel07 May 26, 2026
d4c748c
chore: update env example
DuttaNeel07 May 26, 2026
aa346c1
fix: recaptcha expired issue fixed
DuttaNeel07 May 26, 2026
a8f91a5
fix: duplicate email now shows error in duo team
DuttaNeel07 May 26, 2026
b8cee1c
feat: add relevant error for recaptcha failures
DuttaNeel07 May 26, 2026
90d7528
fix: t&c scroll issue
DuttaNeel07 May 28, 2026
c287173
UI fixes
mkumar1205-beep May 28, 2026
0cdd993
feat: different texts to optimise mobile experience
DuttaNeel07 May 28, 2026
2d427d4
fix: few more
DuttaNeel07 May 28, 2026
e7dbc37
add: DB indexing for faster lookups
DuttaNeel07 May 28, 2026
aadc990
fix: recaptcha verification
DuttaNeel07 Jun 6, 2026
5154f35
add: registration challenge
DuttaNeel07 Jun 7, 2026
92c2852
feat: sections remain locked until prev is finished
DuttaNeel07 Jun 8, 2026
98e2eea
Add login components
mkumar1205-beep May 27, 2026
72f5407
Add dashboard
mkumar1205-beep May 27, 2026
063204a
Login Page
mkumar1205-beep May 27, 2026
298b4f5
Add authentication for dashboard
mkumar1205-beep May 28, 2026
5daef8b
Allow both participants to login
mkumar1205-beep May 28, 2026
458f336
Solve merge conflicts
mkumar1205-beep Jun 8, 2026
5b42098
Add change team name feature
mkumar1205-beep May 29, 2026
425ad41
Add leadership in dashboard
mkumar1205-beep May 29, 2026
aee6d3e
Leave team option in dashboard
mkumar1205-beep May 29, 2026
4f3588d
Add member feature
mkumar1205-beep Jun 1, 2026
423c330
UI Changes
mkumar1205-beep Jun 1, 2026
63de7ae
Fixes
mkumar1205-beep Jun 1, 2026
e8ccbba
UI fixes
mkumar1205-beep Jun 1, 2026
45ed24f
Logic fix
mkumar1205-beep Jun 1, 2026
559ce0a
Fix send invite
mkumar1205-beep Jun 1, 2026
d136dd5
Invite link issue
mkumar1205-beep Jun 1, 2026
7e659d7
Send invite fix:2
mkumar1205-beep Jun 1, 2026
f423318
Send invite link fix
mkumar1205-beep Jun 1, 2026
0ffc41a
ChangeTeamName placeholder fix
mkumar1205-beep Jun 1, 2026
c399cb2
Remove alerts
mkumar1205-beep Jun 2, 2026
3f08e64
Add logout feature
mkumar1205-beep Jun 3, 2026
e164a11
Improve logout
mkumar1205-beep Jun 3, 2026
1471da6
Dashboard UI fixes
mkumar1205-beep Jun 3, 2026
eea71fc
Add secret flag hint and UI fixes
mkumar1205-beep Jun 4, 2026
121fe34
Add whatsapp link in dashboard and redirect to dashboard
mkumar1205-beep Jun 8, 2026
c89a920
Merge conflicts fix
mkumar1205-beep Jun 9, 2026
a74e5ba
fix: extracting ip from headers and env update
DuttaNeel07 Jun 9, 2026
c8f1a28
fix: add radio to additional questions
DuttaNeel07 Jun 9, 2026
5a5a688
Leave team fix
mkumar1205-beep Jun 9, 2026
98fed12
Refresh token fix
mkumar1205-beep Jun 9, 2026
adf506c
fix: build issue
DuttaNeel07 Jun 14, 2026
76f48f2
feat: google ReCaptchaV3
DuttaNeel07 Jun 15, 2026
ebbaa79
fix: rate limiter
DuttaNeel07 Jun 16, 2026
606af79
add: Remove teammate in place of remove member
DuttaNeel07 Jun 16, 2026
7aed3d1
fix: accept-invite route
DuttaNeel07 Jun 16, 2026
c0fdf84
fix: responsiveness in registration form
DuttaNeel07 Jun 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,17 @@ CLOUDINARY_API_SECRET=""
FIRECRAWL_API_KEY=""
GITHUB_TOKEN=""
GITLAB_TOKEN=""
SCRAPE_SECRET=
SCRAPE_URL=
SCRAPE_SECRET=""
SCRAPE_URL=""
UPSTASH_REDIS_REST_URL=""
UPSTASH_REDIS_REST_TOKEN=""
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=""
RECAPTCHA_SECRET_KEY=""
RECAPTCHA_PROJECT=""
RECAPTCHA_API_KEY=""
PBCTF_ACCESS_SECRET=""
PBCTF_REGISTRATION_FLAG=""


# Hellyeah / x-ray tracker
HELLYEAH_TRACKER_ID=""
Expand Down
3 changes: 1 addition & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ ARG NEXT_PUBLIC_DOMAIN
ARG NEXT_PUBLIC_GTM_ID
ENV NEXT_PUBLIC_DOMAIN=$NEXT_PUBLIC_DOMAIN
ENV NEXT_PUBLIC_GTM_ID=$NEXT_PUBLIC_GTM_ID
ENV CLOUDINARY_CLOUD_NAME=$CLOUDINARY_CLOUD_NAME

RUN pnpm run build

Expand Down Expand Up @@ -52,4 +51,4 @@ EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

CMD ["node", "server.js"]
CMD ["node", "server.js"]
56 changes: 56 additions & 0 deletions app/api/pbctf/logout/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
import crypto from "crypto";

import connectDB from "@/lib/db/connection";
import PBCTFRefreshToken from "@/lib/db/models/CTFRefreshToken";

export async function POST() {
await connectDB();

const cookieStore = await cookies();

const refreshToken =
cookieStore.get("pbctf_refresh")?.value;

if (refreshToken) {
const tokenHash = crypto
.createHash("sha256")
.update(refreshToken)
.digest("hex");

await PBCTFRefreshToken.deleteOne({
tokenHash,
});
}

const response = NextResponse.json({
success: true,
});

response.cookies.set(
"pbctf_access",
"",
{
expires: new Date(0),
path: "/",
httpOnly: true,
sameSite: "strict",
secure: process.env.NODE_ENV === "production",
}
);

response.cookies.set(
"pbctf_refresh",
"",
{
expires: new Date(0),
path: "/",
httpOnly: true,
sameSite: "strict",
secure: process.env.NODE_ENV === "production",
}
);

return response;
}
207 changes: 207 additions & 0 deletions app/api/pbctf/refresh/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import { cookies } from "next/headers";
import { NextRequest, NextResponse } from "next/server";
import crypto from "crypto";
import connectDb from "@/lib/db/connection";
import PBCTFRefreshToken from "@/lib/db/models/CTFRefreshToken";
import generateAccessToken from "@/lib/pbctf/generateAccessToken";
import generateRefreshToken from "@/lib/pbctf/generateRefreshToken";

type RefreshResult =
| {
success: true;
accessToken: string;
refreshToken: string;
}
| {
success: false;
status: number;
error: string;
};

const cookieOptions = {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict" as const,
path: "/",
};

function clearAuthCookies(response: NextResponse) {
response.cookies.set("pbctf_access", "", {
...cookieOptions,
expires: new Date(0),
});

response.cookies.set("pbctf_refresh", "", {
...cookieOptions,
expires: new Date(0),
});

return response;
}

function setAuthCookies(
response: NextResponse,
accessToken: string,
refreshToken: string
) {
response.cookies.set("pbctf_access", accessToken, {
...cookieOptions,
maxAge: 60 * 10,
});

response.cookies.set("pbctf_refresh", refreshToken, {
...cookieOptions,
maxAge: 24 * 60 * 60,
});

return response;
}

async function rotateRefreshToken(): Promise<RefreshResult> {
await connectDb();

const cookieStore = await cookies();
const refreshToken =
cookieStore.get("pbctf_refresh")?.value;

if (!refreshToken) {
return {
success: false,
status: 401,
error: "Refresh token missing",
};
}

const tokenHash = crypto
.createHash("sha256")
.update(refreshToken)
.digest("hex");

const matchedToken =
await PBCTFRefreshToken.findOne({
tokenHash,
});

if (!matchedToken) {
return {
success: false,
status: 401,
error: "Invalid refresh token",
};
}

if (matchedToken.expiresAt < new Date()) {
await matchedToken.deleteOne();

return {
success: false,
status: 401,
error: "Refresh token expired",
};
}

const newAccessToken =
generateAccessToken(matchedToken.email);
const newRefreshToken =
generateRefreshToken();

const newRefreshHash = crypto
.createHash("sha256")
.update(newRefreshToken)
.digest("hex");

matchedToken.tokenHash = newRefreshHash;
matchedToken.expiresAt = new Date(
Date.now() + 24 * 60 * 60 * 1000
);

await matchedToken.save();

return {
success: true,
accessToken: newAccessToken,
refreshToken: newRefreshToken,
};
}

function getSafeNextUrl(request: NextRequest) {
const next =
request.nextUrl.searchParams.get("next") ||
"/pbctf/dashboard";

if (!next.startsWith("/") || next.startsWith("//")) {
return "/pbctf/dashboard";
}

return next;
}

export async function GET(request: NextRequest) {
try {
const result = await rotateRefreshToken();

if (!result.success) {
const response = NextResponse.redirect(
new URL("/pbctf/login", request.url)
);

return clearAuthCookies(response);
}

const response = NextResponse.redirect(
new URL(getSafeNextUrl(request), request.url)
);

return setAuthCookies(
response,
result.accessToken,
result.refreshToken
);
} catch (error) {
console.error(error);

return NextResponse.redirect(
new URL("/pbctf/login", request.url)
);
}
}

export async function POST() {
try {
const result = await rotateRefreshToken();

if (!result.success) {
const response = NextResponse.json(
{
error: result.error,
},
{
status: result.status,
}
);

return clearAuthCookies(response);
}

const response = NextResponse.json({
success: true,
});

return setAuthCookies(
response,
result.accessToken,
result.refreshToken
);
} catch (error) {
console.error(error);

return NextResponse.json(
{
error: "Internal server error",
},
{
status: 500,
}
);
}
}
Loading
Loading