From f040f45ad9871b33aca280c8bcc629f8d2d1af7a Mon Sep 17 00:00:00 2001 From: michelleyeoh Date: Sun, 8 Mar 2026 21:07:54 -0700 Subject: [PATCH 1/5] restrict admin users --- .../ProtectedDisplay/ProtectedDisplay.tsx | 6 ++++++ app/(pages)/admin/layout.tsx | 16 +++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/app/(pages)/_components/ProtectedDisplay/ProtectedDisplay.tsx b/app/(pages)/_components/ProtectedDisplay/ProtectedDisplay.tsx index 6ea887a8..5b78bda8 100644 --- a/app/(pages)/_components/ProtectedDisplay/ProtectedDisplay.tsx +++ b/app/(pages)/_components/ProtectedDisplay/ProtectedDisplay.tsx @@ -4,15 +4,21 @@ import getActiveUser from 'app/(pages)/_utils/getActiveUser'; export default async function ProtectedDisplay({ allowedRoles, + allowedUsers, failRedirectRoute, children, }: { allowedRoles: string[]; + allowedUsers?: string[]; failRedirectRoute: string; children: React.ReactNode; }) { const user = await getActiveUser(failRedirectRoute); + if (allowedUsers && !allowedUsers.includes(user.email)) { + redirect('/'); + } + const authorized = allowedRoles.includes(user.role); if (user.role === 'hacker') { diff --git a/app/(pages)/admin/layout.tsx b/app/(pages)/admin/layout.tsx index ddc1f0f1..0aff7436 100644 --- a/app/(pages)/admin/layout.tsx +++ b/app/(pages)/admin/layout.tsx @@ -10,8 +10,22 @@ export default function AdminLayout({ }: { children: React.ReactNode; }) { + const adminEmail = process.env.HUB_ADMIN_EMAIL; + + if (!adminEmail) { + console.warn( + 'HUB_ADMIN_EMAIL environment variable is not set, no users will have access to the admin panel' + ); + } + + const parsedAdminEmail = adminEmail ? adminEmail : ''; + return ( - + {children} ); From 974962df6590a8789a7f026173f1eaaee00dae18 Mon Sep 17 00:00:00 2001 From: michelleyeoh Date: Mon, 4 May 2026 22:53:41 -0700 Subject: [PATCH 2/5] added env var to workflows --- .github/workflows/production.yaml | 1 + .github/workflows/staging.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/production.yaml b/.github/workflows/production.yaml index 59238564..2e34c572 100644 --- a/.github/workflows/production.yaml +++ b/.github/workflows/production.yaml @@ -49,6 +49,7 @@ jobs: printf "${{ secrets.CHECK_IN_CODE }}" | vercel env add CHECK_IN_CODE production --force --token=${{ secrets.VERCEL_TOKEN }} printf "${{ secrets.TITO_API_TOKEN }}" | vercel env add TITO_API_TOKEN production --force --token=${{ secrets.VERCEL_TOKEN }} printf "${{ secrets.OPENAI_API_KEY }}" | vercel env add OPENAI_API_KEY production --force --token=${{ secrets.VERCEL_TOKEN }} + printf "${{ secrets.HUB_ADMIN_EMAIL }}" | vercel env add HUB_ADMIN_EMAIL production --force --token=${{ secrets.VERCEL_TOKEN }} printf "${{ vars.ENV_URL }}" | vercel env add BASE_URL production --force --token=${{ secrets.VERCEL_TOKEN }} printf "${{ vars.INVITE_DEADLINE }}" | vercel env add INVITE_DEADLINE production --force --token=${{ secrets.VERCEL_TOKEN }} diff --git a/.github/workflows/staging.yaml b/.github/workflows/staging.yaml index 5171adc1..7d487cea 100644 --- a/.github/workflows/staging.yaml +++ b/.github/workflows/staging.yaml @@ -51,6 +51,7 @@ jobs: printf "${{ secrets.CHECK_IN_CODE }}" | vercel env add CHECK_IN_CODE production --force --token=${{ secrets.VERCEL_TOKEN }} printf "${{ secrets.TITO_API_TOKEN }}" | vercel env add TITO_API_TOKEN production --force --token=${{ secrets.VERCEL_TOKEN }} printf "${{ secrets.OPENAI_API_KEY }}" | vercel env add OPENAI_API_KEY production --force --token=${{ secrets.VERCEL_TOKEN }} + printf "${{ secrets.HUB_ADMIN_EMAIL }}" | vercel env add HUB_ADMIN_EMAIL production --force --token=${{ secrets.VERCEL_TOKEN }} printf "${{ vars.ENV_URL }}" | vercel env add BASE_URL production --force --token=${{ secrets.VERCEL_TOKEN }} printf "${{ vars.INVITE_DEADLINE }}" | vercel env add INVITE_DEADLINE production --force --token=${{ secrets.VERCEL_TOKEN }} From 082428f0bad43cd7e3ab1ffdc71b3c0f7d041be5 Mon Sep 17 00:00:00 2001 From: michelleyeoh Date: Mon, 4 May 2026 23:13:40 -0700 Subject: [PATCH 3/5] added normaliztion on email and singular admin emai logic --- .../ProtectedDisplay/ProtectedDisplay.tsx | 14 ++++++++++---- app/(pages)/admin/layout.tsx | 3 ++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/app/(pages)/_components/ProtectedDisplay/ProtectedDisplay.tsx b/app/(pages)/_components/ProtectedDisplay/ProtectedDisplay.tsx index 5b78bda8..071c2317 100644 --- a/app/(pages)/_components/ProtectedDisplay/ProtectedDisplay.tsx +++ b/app/(pages)/_components/ProtectedDisplay/ProtectedDisplay.tsx @@ -4,19 +4,25 @@ import getActiveUser from 'app/(pages)/_utils/getActiveUser'; export default async function ProtectedDisplay({ allowedRoles, - allowedUsers, + allowedUser, failRedirectRoute, children, }: { allowedRoles: string[]; - allowedUsers?: string[]; + allowedUser?: string; failRedirectRoute: string; children: React.ReactNode; }) { const user = await getActiveUser(failRedirectRoute); - if (allowedUsers && !allowedUsers.includes(user.email)) { - redirect('/'); + if (allowedUser) { + const normalizedAllowedUser = allowedUser.trim().toLowerCase(); + const userEmail = String(user.email ?? '') + .trim() + .toLowerCase(); + if (!normalizedAllowedUser.includes(userEmail)) { + redirect(failRedirectRoute); + } } const authorized = allowedRoles.includes(user.role); diff --git a/app/(pages)/admin/layout.tsx b/app/(pages)/admin/layout.tsx index 0aff7436..12a8ec50 100644 --- a/app/(pages)/admin/layout.tsx +++ b/app/(pages)/admin/layout.tsx @@ -18,12 +18,13 @@ export default function AdminLayout({ ); } + // Assuming only one admin email const parsedAdminEmail = adminEmail ? adminEmail : ''; return ( {children} From b64e513d57bb1c634592bf95836a111c7be9f5aa Mon Sep 17 00:00:00 2001 From: michelleyeoh Date: Mon, 4 May 2026 23:15:59 -0700 Subject: [PATCH 4/5] fix build error --- app/(pages)/admin/layout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/(pages)/admin/layout.tsx b/app/(pages)/admin/layout.tsx index 12a8ec50..c385ef0d 100644 --- a/app/(pages)/admin/layout.tsx +++ b/app/(pages)/admin/layout.tsx @@ -24,7 +24,7 @@ export default function AdminLayout({ return ( {children} From e29c06254dd891f8ff880c7a235249f5a8da64d3 Mon Sep 17 00:00:00 2001 From: michelleyeoh <74385095+michelleyeoh@users.noreply.github.com> Date: Mon, 4 May 2026 23:18:16 -0700 Subject: [PATCH 5/5] hard check on admin email Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- app/(pages)/_components/ProtectedDisplay/ProtectedDisplay.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/(pages)/_components/ProtectedDisplay/ProtectedDisplay.tsx b/app/(pages)/_components/ProtectedDisplay/ProtectedDisplay.tsx index 071c2317..14a53c74 100644 --- a/app/(pages)/_components/ProtectedDisplay/ProtectedDisplay.tsx +++ b/app/(pages)/_components/ProtectedDisplay/ProtectedDisplay.tsx @@ -20,7 +20,8 @@ export default async function ProtectedDisplay({ const userEmail = String(user.email ?? '') .trim() .toLowerCase(); - if (!normalizedAllowedUser.includes(userEmail)) { + + if (!userEmail || normalizedAllowedUser !== userEmail) { redirect(failRedirectRoute); } }