From fd303ee5a4c8d317251ddd66b7b2d6df9ad93fc3 Mon Sep 17 00:00:00 2001 From: Umair Hundekar Date: Mon, 29 Sep 2025 21:21:42 -0400 Subject: [PATCH 1/5] Added Admin authentication --- backend/app/routes/auth.py | 5 + frontend/src/pages/admin-login.tsx | 8 +- frontend/src/pages/admin-signup.tsx | 276 +++++++++++++++++++++++++ frontend/src/pages/admin-verify.tsx | 189 +++++++++++++++++ frontend/src/pages/admin/dashboard.tsx | 25 +-- 5 files changed, 477 insertions(+), 26 deletions(-) create mode 100644 frontend/src/pages/admin-signup.tsx create mode 100644 frontend/src/pages/admin-verify.tsx diff --git a/backend/app/routes/auth.py b/backend/app/routes/auth.py index e2abd2c9..0c7764ed 100644 --- a/backend/app/routes/auth.py +++ b/backend/app/routes/auth.py @@ -17,6 +17,11 @@ # TODO: ADD RATE LIMITING @router.post("/register", response_model=UserCreateResponse) async def register_user(user: UserCreateRequest, user_service: UserService = Depends(get_user_service)): + allowed_Admins = ["umair.hundekar@uwblueprint.org", "umairmhundekar@gmail.com"] + if user.role == UserRole.ADMIN: + if user.email not in allowed_Admins: + raise HTTPException(status_code=403, detail="Access denied. Admin privileges required for admin portal") + try: return await user_service.create_user(user) except HTTPException as http_ex: diff --git a/frontend/src/pages/admin-login.tsx b/frontend/src/pages/admin-login.tsx index 704e3952..cc3b0bbd 100644 --- a/frontend/src/pages/admin-login.tsx +++ b/frontend/src/pages/admin-login.tsx @@ -23,11 +23,11 @@ export default function AdminLogin() { try { const result = await login(email, password); - if (result) { + if (result.success) { console.log('Admin login success:', result); router.push('/admin/dashboard'); } else { - setError('Invalid email or password'); + setError('Invalid email or password. Please check your credentials and try again. If you recently signed up, make sure to verify your email first.'); } } catch (err: unknown) { console.error('Admin login error:', err); @@ -163,7 +163,7 @@ export default function AdminLogin() { marginTop: 6, cursor: 'pointer', }} - onClick={() => router.push('/reset-password')} + onClick={() => router.push('/admin-reset-password')} > Forgot Password? @@ -203,7 +203,7 @@ export default function AdminLogin() { > Don't have an account?{' '} { + e.preventDefault(); + setError(''); + + if (password !== confirmPassword) { + setError('Passwords do not match'); + return; + } + + try { + const userData = { + first_name: '', + last_name: '', + email, + password, + role: UserRole.ADMIN, + signupMethod: SignUpMethod.PASSWORD, + }; + const result = await register(userData); + console.log('Admin registration success:', result); + router.push(`/admin-verify?email=${encodeURIComponent(email)}&role=admin`); + } catch (err: unknown) { + console.error('Admin registration error:', err); + if ( + err && + typeof err === 'object' && + 'response' in err && + err.response && + typeof err.response === 'object' && + 'data' in err.response && + err.response.data && + typeof err.response.data === 'object' && + 'detail' in err.response.data + ) { + setError((err.response.data as { detail: string }).detail || 'Registration failed'); + } else { + setError('Registration failed'); + } + } + }; + + return ( + + {/* Left: Admin Login Form */} + + + + Admin Portal - First Connection Peer +
+ Support Program +
+ + Welcome! + + + Let's start by setting up your admin account. + +
+ + Email + + } + mb={4} + > + + setEmail(e.target.value)} + /> + + + + Password + + } + mb={2} + > + + setPassword(e.target.value)} + /> + + + + Confirm Password + + } + mb={6} + > + + setConfirmPassword(e.target.value)} + /> + + + {error && ( + + {typeof error === 'string' ? error : JSON.stringify(error)} + + )} + +
+ + Already have an account?{' '} + + Sign in. + + +
+
+ {/* Right: Image */} + + Admin Portal Visual + +
+ ); +} diff --git a/frontend/src/pages/admin-verify.tsx b/frontend/src/pages/admin-verify.tsx new file mode 100644 index 00000000..03b3105e --- /dev/null +++ b/frontend/src/pages/admin-verify.tsx @@ -0,0 +1,189 @@ +import { useRouter } from 'next/router'; +import { Box, Flex, Heading, Text, Link } from '@chakra-ui/react'; +import { useEffect, useState } from 'react'; +import { useEmailVerification } from '@/hooks/useEmailVerification'; +import { + getEmailForSignIn, + clearEmailForSignIn, + setEmailForSignIn, +} from '@/services/firebaseAuthService'; +import { auth } from '@/config/firebase'; +import Image from 'next/image'; + +const veniceBlue = '#1d3448'; + +export default function AdminVerifyPage() { + const router = useRouter(); + const { email, role } = router.query; + const [displayEmail, setDisplayEmail] = useState(''); + const [message, setMessage] = useState<{ type: 'success' | 'error' | null; text: string }>({ + type: null, + text: '', + }); + + const { sendVerificationEmail, sendSignInLink, isLoading, error, success } = + useEmailVerification(); + + useEffect(() => { + // Get email from query params or localStorage + const emailFromQuery = email as string; + const emailFromStorage = getEmailForSignIn(); + const finalEmail = emailFromQuery || emailFromStorage || 'admin@example.com'; + setDisplayEmail(finalEmail); + + // Store the email from query params if available + if (emailFromQuery) { + setEmailForSignIn(emailFromQuery); + } + }, [email]); + + useEffect(() => { + if (success) { + setMessage({ type: 'success', text: 'Email sent successfully! Please check your inbox.' }); + } + }, [success]); + + useEffect(() => { + if (error) { + setMessage({ type: 'error', text: error }); + } + }, [error]); + + const handleResendEmail = async () => { + setMessage({ type: null, text: '' }); + + // For admin users, send verification email instead of sign-in link + await sendVerificationEmail(); + }; + + const handleBackToLogin = () => { + clearEmailForSignIn(); + router.push('/admin-login'); + }; + + return ( + + {/* Left: Admin Verification Content */} + + + + Admin Portal - First Connection Peer Support Program + + + Verify Your Admin Account + + + We sent a confirmation link to {displayEmail} + + + {message.type === 'success' && ( + + {message.text} + + )} + + {message.type === 'error' && ( + + {message.text} + + )} + + + Didn't get a link?{' '} + + Click here to resend. + + + + + Remember your password?{' '} + + Back to login + + + + + {/* Right: Image - Using admin.png from admin-login.tsx */} + + Admin Portal Visual + + + ); +} diff --git a/frontend/src/pages/admin/dashboard.tsx b/frontend/src/pages/admin/dashboard.tsx index 3af94c2b..2edf9505 100644 --- a/frontend/src/pages/admin/dashboard.tsx +++ b/frontend/src/pages/admin/dashboard.tsx @@ -1,6 +1,6 @@ import React from 'react'; import Image from 'next/image'; -import { Box, Flex, Heading, Text, Link } from '@chakra-ui/react'; +import { Box, Flex, Heading, Text } from '@chakra-ui/react'; import { ProtectedPage } from '@/components/auth/ProtectedPage'; import { UserRole } from '@/types/authTypes'; @@ -41,7 +41,7 @@ export default function AdminDashboard() { mb={6} mt={8} > - Welcome! + You've logged in! - We sent a confirmation link to john.doe@gmail.com - - - Didn't get a link?{' '} - - Click here to resend. - + Welcome to the admin dashboard. You have successfully logged in to your administrator account. From 91f7886181cddc200caeeedc8add2031ad18201b Mon Sep 17 00:00:00 2001 From: Umair Hundekar Date: Tue, 7 Oct 2025 13:23:33 -0400 Subject: [PATCH 2/5] rebased --- backend/app/routes/auth.py | 2 +- frontend/src/APIClients/authAPIClient.ts | 46 ++-- frontend/src/pages/admin-login.tsx | 2 +- frontend/src/pages/admin-signup.tsx | 34 ++- frontend/src/pages/admin.tsx | 276 ----------------------- 5 files changed, 48 insertions(+), 312 deletions(-) delete mode 100644 frontend/src/pages/admin.tsx diff --git a/backend/app/routes/auth.py b/backend/app/routes/auth.py index 0c7764ed..e6d2f27b 100644 --- a/backend/app/routes/auth.py +++ b/backend/app/routes/auth.py @@ -17,7 +17,7 @@ # TODO: ADD RATE LIMITING @router.post("/register", response_model=UserCreateResponse) async def register_user(user: UserCreateRequest, user_service: UserService = Depends(get_user_service)): - allowed_Admins = ["umair.hundekar@uwblueprint.org", "umairmhundekar@gmail.com"] + allowed_Admins = ["umair.hkar@gmail.com", "umairmhundekar@gmail.com"] if user.role == UserRole.ADMIN: if user.email not in allowed_Admins: raise HTTPException(status_code=403, detail="Access denied. Admin privileges required for admin portal") diff --git a/frontend/src/APIClients/authAPIClient.ts b/frontend/src/APIClients/authAPIClient.ts index 9ad7cfcc..cdfab868 100644 --- a/frontend/src/APIClients/authAPIClient.ts +++ b/frontend/src/APIClients/authAPIClient.ts @@ -35,7 +35,7 @@ export interface AuthResult { validationErrors?: string[]; } -export const login = async (email: string, password: string): Promise => { +export const login = async (email: string, password: string, isAdminPortal: boolean = false): Promise => { try { // Validate inputs if (!validateEmail(email)) { @@ -63,12 +63,29 @@ export const login = async (email: string, password: string): Promise('/auth/login', loginRequest, { - withCredentials: true, - }); + const headers: any = { withCredentials: true }; + + // Add admin portal header if this is an admin login + if (isAdminPortal) { + headers.headers = { 'X-Admin-Portal': 'true' }; + } + + const { data } = await baseAPIClient.post('/auth/login', loginRequest, headers); localStorage.setItem(AUTHENTICATED_USER_KEY, JSON.stringify(data)); - return { success: true, user: { ...data.user, ...data } as AuthenticatedUser }; - } catch { + return { success: true, user: { ...data.user, ...data } }; + } catch (error) { + // Handle admin privilege errors specifically + if (error && typeof error === 'object' && 'response' in error) { + const response = (error as { response?: { status?: number; data?: { detail?: string } } }).response; + if (response?.status === 403 && isAdminPortal) { + return { + success: false, + error: 'Access denied. You do not have admin privileges. Please contact an administrator.', + errorCode: 'auth/insufficient-privileges', + }; + } + } + // Backend login failure is not critical since Firebase auth succeeded return { success: true, @@ -217,22 +234,19 @@ export const register = async ({ } else { console.warn('[REGISTER] Failed to send email verification after registration'); } + + // Return success with user info - don't try to login since email isn't verified yet + return { + success: true, + user: { email: user.email, uid: user.uid } as unknown as AuthenticatedUser, + }; } catch (firebaseError) { console.error('[REGISTER] Firebase sign-in failed:', firebaseError); // Continue with registration even if Firebase sign-in fails // The user can still verify their email later - } - - // Try backend login but don't fail if it doesn't work - try { - const loginResult = await login(email, password); - return loginResult; - } catch (loginError) { - console.warn('[REGISTER] Backend login failed, but registration was successful:', loginError); - // Return success even if backend login fails, since Firebase user was created return { success: true, - user: { email, uid: auth.currentUser?.uid || 'unknown' } as unknown as AuthenticatedUser, + user: { email, uid: 'unknown' } as unknown as AuthenticatedUser, }; } } catch (error) { diff --git a/frontend/src/pages/admin-login.tsx b/frontend/src/pages/admin-login.tsx index cc3b0bbd..20e8aec3 100644 --- a/frontend/src/pages/admin-login.tsx +++ b/frontend/src/pages/admin-login.tsx @@ -163,7 +163,7 @@ export default function AdminLogin() { marginTop: 6, cursor: 'pointer', }} - onClick={() => router.push('/admin-reset-password')} + onClick={() => router.push('/reset-password')} > Forgot Password? diff --git a/frontend/src/pages/admin-signup.tsx b/frontend/src/pages/admin-signup.tsx index b98fe141..ae647b16 100644 --- a/frontend/src/pages/admin-signup.tsx +++ b/frontend/src/pages/admin-signup.tsx @@ -38,25 +38,23 @@ export default function AdminLoginPage() { signupMethod: SignUpMethod.PASSWORD, }; const result = await register(userData); - console.log('Admin registration success:', result); - router.push(`/admin-verify?email=${encodeURIComponent(email)}&role=admin`); - } catch (err: unknown) { - console.error('Admin registration error:', err); - if ( - err && - typeof err === 'object' && - 'response' in err && - err.response && - typeof err.response === 'object' && - 'data' in err.response && - err.response.data && - typeof err.response.data === 'object' && - 'detail' in err.response.data - ) { - setError((err.response.data as { detail: string }).detail || 'Registration failed'); + console.log("?", result) + // Check if it's an admin privilege error + if (!result.success && result.error && result.error.includes('Admin privileges required')) { + setError('Access denied. Admin registration is restricted. Please contact an administrator.'); + return; + } + + // If successful (even if success is false, check if we got a user) + if (result.user || result.success) { + console.log('Admin registration success:', result); + router.push(`/admin-verify?email=${encodeURIComponent(email)}&role=admin`); } else { - setError('Registration failed'); + setError(result.error || 'Registration failed'); } + } catch (err: unknown) { + console.error('Admin registration error:', err); + setError('Registration failed'); } }; @@ -247,7 +245,7 @@ export default function AdminLoginPage() { > Already have an account?{' '} { - e.preventDefault(); - setError(''); - - if (password !== confirmPassword) { - setError('Passwords do not match'); - return; - } - - try { - const userData = { - first_name: '', - last_name: '', - email, - password, - role: UserRole.ADMIN, - signupMethod: SignUpMethod.PASSWORD, - }; - const result = await register(userData); - console.log('Admin registration success:', result); - router.push(`/verify?email=${encodeURIComponent(email)}&role=admin`); - } catch (err: unknown) { - console.error('Admin registration error:', err); - if ( - err && - typeof err === 'object' && - 'response' in err && - err.response && - typeof err.response === 'object' && - 'data' in err.response && - err.response.data && - typeof err.response.data === 'object' && - 'detail' in err.response.data - ) { - setError((err.response.data as { detail: string }).detail || 'Registration failed'); - } else { - setError('Registration failed'); - } - } - }; - - return ( - - {/* Left: Admin Login Form */} - - - - Admin Portal - First Connection Peer -
- Support Program -
- - Welcome! - - - Let's start by setting up your admin account. - -
- - Email - - } - mb={4} - > - - setEmail(e.target.value)} - /> - - - - Password - - } - mb={2} - > - - setPassword(e.target.value)} - /> - - - - Confirm Password - - } - mb={6} - > - - setConfirmPassword(e.target.value)} - /> - - - {error && ( - - {typeof error === 'string' ? error : JSON.stringify(error)} - - )} - -
- - Already have an account?{' '} - - Sign in. - - -
-
- {/* Right: Image */} - - Admin Portal Visual - -
- ); -} From c88972b43b2bdcf2cbc441427d58a73612de35b3 Mon Sep 17 00:00:00 2001 From: Umair Hundekar Date: Tue, 7 Oct 2025 13:25:39 -0400 Subject: [PATCH 3/5] rebase pt 2 --- backend/app/routes/auth.py | 2 +- frontend/src/APIClients/authAPIClient.ts | 12 +++++++----- frontend/src/pages/admin-login.tsx | 4 +++- frontend/src/pages/admin-signup.tsx | 8 +++++--- frontend/src/pages/admin/dashboard.tsx | 3 ++- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/backend/app/routes/auth.py b/backend/app/routes/auth.py index e6d2f27b..dce36793 100644 --- a/backend/app/routes/auth.py +++ b/backend/app/routes/auth.py @@ -21,7 +21,7 @@ async def register_user(user: UserCreateRequest, user_service: UserService = Dep if user.role == UserRole.ADMIN: if user.email not in allowed_Admins: raise HTTPException(status_code=403, detail="Access denied. Admin privileges required for admin portal") - + try: return await user_service.create_user(user) except HTTPException as http_ex: diff --git a/frontend/src/APIClients/authAPIClient.ts b/frontend/src/APIClients/authAPIClient.ts index cdfab868..51a1deca 100644 --- a/frontend/src/APIClients/authAPIClient.ts +++ b/frontend/src/APIClients/authAPIClient.ts @@ -64,28 +64,30 @@ export const login = async (email: string, password: string, isAdminPortal: bool try { const loginRequest: LoginRequest = { email, password }; const headers: any = { withCredentials: true }; - + // Add admin portal header if this is an admin login if (isAdminPortal) { headers.headers = { 'X-Admin-Portal': 'true' }; } - + const { data } = await baseAPIClient.post('/auth/login', loginRequest, headers); localStorage.setItem(AUTHENTICATED_USER_KEY, JSON.stringify(data)); return { success: true, user: { ...data.user, ...data } }; } catch (error) { // Handle admin privilege errors specifically if (error && typeof error === 'object' && 'response' in error) { - const response = (error as { response?: { status?: number; data?: { detail?: string } } }).response; + const response = (error as { response?: { status?: number; data?: { detail?: string } } }) + .response; if (response?.status === 403 && isAdminPortal) { return { success: false, - error: 'Access denied. You do not have admin privileges. Please contact an administrator.', + error: + 'Access denied. You do not have admin privileges. Please contact an administrator.', errorCode: 'auth/insufficient-privileges', }; } } - + // Backend login failure is not critical since Firebase auth succeeded return { success: true, diff --git a/frontend/src/pages/admin-login.tsx b/frontend/src/pages/admin-login.tsx index 20e8aec3..2359d841 100644 --- a/frontend/src/pages/admin-login.tsx +++ b/frontend/src/pages/admin-login.tsx @@ -27,7 +27,9 @@ export default function AdminLogin() { console.log('Admin login success:', result); router.push('/admin/dashboard'); } else { - setError('Invalid email or password. Please check your credentials and try again. If you recently signed up, make sure to verify your email first.'); + setError( + 'Invalid email or password. Please check your credentials and try again. If you recently signed up, make sure to verify your email first.', + ); } } catch (err: unknown) { console.error('Admin login error:', err); diff --git a/frontend/src/pages/admin-signup.tsx b/frontend/src/pages/admin-signup.tsx index ae647b16..9eee1860 100644 --- a/frontend/src/pages/admin-signup.tsx +++ b/frontend/src/pages/admin-signup.tsx @@ -38,13 +38,15 @@ export default function AdminLoginPage() { signupMethod: SignUpMethod.PASSWORD, }; const result = await register(userData); - console.log("?", result) + console.log('?', result); // Check if it's an admin privilege error if (!result.success && result.error && result.error.includes('Admin privileges required')) { - setError('Access denied. Admin registration is restricted. Please contact an administrator.'); + setError( + 'Access denied. Admin registration is restricted. Please contact an administrator.', + ); return; } - + // If successful (even if success is false, check if we got a user) if (result.user || result.success) { console.log('Admin registration success:', result); diff --git a/frontend/src/pages/admin/dashboard.tsx b/frontend/src/pages/admin/dashboard.tsx index 2edf9505..6db15f30 100644 --- a/frontend/src/pages/admin/dashboard.tsx +++ b/frontend/src/pages/admin/dashboard.tsx @@ -50,7 +50,8 @@ export default function AdminDashboard() { fontWeight={400} fontSize="lg" > - Welcome to the admin dashboard. You have successfully logged in to your administrator account. + Welcome to the admin dashboard. You have successfully logged in to your administrator + account. From ebbff45360aa146cecd5a016c311e32a105f5a8e Mon Sep 17 00:00:00 2001 From: Umair Hundekar Date: Tue, 7 Oct 2025 13:29:49 -0400 Subject: [PATCH 4/5] fixed linter --- frontend/src/APIClients/authAPIClient.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/src/APIClients/authAPIClient.ts b/frontend/src/APIClients/authAPIClient.ts index 51a1deca..a753aa50 100644 --- a/frontend/src/APIClients/authAPIClient.ts +++ b/frontend/src/APIClients/authAPIClient.ts @@ -35,7 +35,11 @@ export interface AuthResult { validationErrors?: string[]; } -export const login = async (email: string, password: string, isAdminPortal: boolean = false): Promise => { +export const login = async ( + email: string, + password: string, + isAdminPortal: boolean = false, +): Promise => { try { // Validate inputs if (!validateEmail(email)) { From d7e130e65404b007f2705cc597a989b43d7b3244 Mon Sep 17 00:00:00 2001 From: Umair Hundekar Date: Tue, 7 Oct 2025 13:43:48 -0400 Subject: [PATCH 5/5] Allowed for verfiication through log in page --- frontend/src/pages/admin-login.tsx | 7 +++---- frontend/src/pages/index.tsx | 2 ++ 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/src/pages/admin-login.tsx b/frontend/src/pages/admin-login.tsx index 2359d841..e2b07efb 100644 --- a/frontend/src/pages/admin-login.tsx +++ b/frontend/src/pages/admin-login.tsx @@ -24,12 +24,11 @@ export default function AdminLogin() { try { const result = await login(email, password); if (result.success) { - console.log('Admin login success:', result); router.push('/admin/dashboard'); + } else if (result.errorCode === 'auth/email-not-verified') { + router.push(`/admin-verify?email=${encodeURIComponent(email)}&role=admin`); } else { - setError( - 'Invalid email or password. Please check your credentials and try again. If you recently signed up, make sure to verify your email first.', - ); + setError('Invalid email or password. Please check your credentials and try again.'); } } catch (err: unknown) { console.error('Admin login error:', err); diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx index 784fe1af..cfe1aadb 100644 --- a/frontend/src/pages/index.tsx +++ b/frontend/src/pages/index.tsx @@ -37,6 +37,8 @@ export default function LoginPage() { if (result.success) { router.push('/welcome'); + } else if (result.errorCode === 'auth/email-not-verified') { + router.push(`/verify?email=${encodeURIComponent(email)}`); } else { setError(result.error || 'Login failed. Please try again.'); }