From ae51522442878aff986aa4e16902dc891c8c7724 Mon Sep 17 00:00:00 2001 From: Charlie Bailey Date: Wed, 25 Mar 2026 15:16:48 -0700 Subject: [PATCH 1/3] Updated backend documentation and variable names to reflect that auth now expects a hashed password. --- .../app/api/routes/auth.py | 4 ++-- multi_llm_chatbot_backend/app/core/auth.py | 18 +++++++++--------- multi_llm_chatbot_backend/app/models/user.py | 4 ++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/multi_llm_chatbot_backend/app/api/routes/auth.py b/multi_llm_chatbot_backend/app/api/routes/auth.py index 37c00f80..6b45d339 100644 --- a/multi_llm_chatbot_backend/app/api/routes/auth.py +++ b/multi_llm_chatbot_backend/app/api/routes/auth.py @@ -33,7 +33,7 @@ async def signup(user_data: UserCreate): ) # Create new user - hashed_password = get_password_hash(user_data.password) + hashed_password = get_password_hash(user_data.password_hash) user = User( firstName=user_data.firstName, lastName=user_data.lastName, @@ -76,7 +76,7 @@ async def login(user_credentials: UserLogin): """Login with email and password""" try: # Authenticate user - user = await authenticate_user(user_credentials.email, user_credentials.password) + user = await authenticate_user(user_credentials.email, user_credentials.password_hash) if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, diff --git a/multi_llm_chatbot_backend/app/core/auth.py b/multi_llm_chatbot_backend/app/core/auth.py index 4fa03770..b66d1e27 100644 --- a/multi_llm_chatbot_backend/app/core/auth.py +++ b/multi_llm_chatbot_backend/app/core/auth.py @@ -22,13 +22,13 @@ # Security scheme security = HTTPBearer() -def verify_password(plain_password: str, hashed_password: str) -> bool: - """Verify a password against its hash""" - return pwd_context.verify(plain_password, hashed_password) +def verify_password(password_hash: str, hashed_password: str) -> bool: + """Verify a client-provided SHA-256 password hash against the stored bcrypt hash""" + return pwd_context.verify(password_hash, hashed_password) -def get_password_hash(password: str) -> str: - """Hash a password""" - return pwd_context.hash(password) +def get_password_hash(password_hash: str) -> str: + """Bcrypt-hash a client-provided SHA-256 password hash for storage""" + return pwd_context.hash(password_hash) def create_access_token(data: dict, expires_delta: Optional[timedelta] = None): """Create a JWT access token""" @@ -61,12 +61,12 @@ async def get_user_by_id(user_id: str) -> Optional[User]: except Exception: return None -async def authenticate_user(email: str, password: str) -> Optional[User]: - """Authenticate user with email and password""" +async def authenticate_user(email: str, password_hash: str) -> Optional[User]: + """Authenticate user with email and client-provided SHA-256 password hash""" user = await get_user_by_email(email) if not user: return None - if not verify_password(password, user.hashed_password): + if not verify_password(password_hash, user.hashed_password): return None return user diff --git a/multi_llm_chatbot_backend/app/models/user.py b/multi_llm_chatbot_backend/app/models/user.py index 95d1067c..8df903c2 100644 --- a/multi_llm_chatbot_backend/app/models/user.py +++ b/multi_llm_chatbot_backend/app/models/user.py @@ -25,13 +25,13 @@ class UserCreate(BaseModel): firstName: str lastName: str email: EmailStr - password: str + password_hash: str academicStage: Optional[str] = None researchArea: Optional[str] = None class UserLogin(BaseModel): email: EmailStr - password: str + password_hash: str class User(BaseModel): model_config = ConfigDict( From 5ca4d16cb297736de95ac4d258f617d66e858669 Mon Sep 17 00:00:00 2001 From: Charlie Bailey Date: Wed, 25 Mar 2026 15:37:56 -0700 Subject: [PATCH 2/3] Added SHA-256 password hashing utility for client-side use. --- phd-advisor-frontend/src/utils/hashPassword.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 phd-advisor-frontend/src/utils/hashPassword.js diff --git a/phd-advisor-frontend/src/utils/hashPassword.js b/phd-advisor-frontend/src/utils/hashPassword.js new file mode 100644 index 00000000..9e26dd57 --- /dev/null +++ b/phd-advisor-frontend/src/utils/hashPassword.js @@ -0,0 +1,14 @@ +/** + * Hash a password client-side before sending to the backend, + * so the server never receives plaintext credentials. + * + * @param {string} password - The user's plaintext password + * @returns {Promise} 64-character lowercase SHA-256 hex digest + */ +export async function hashPassword(password) { + const encoder = new TextEncoder(); + const data = encoder.encode(password); + const hashBuffer = await crypto.subtle.digest('SHA-256', data); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + return hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); +} From 587948e6959935d561ccbbe275fa978ec962a46a Mon Sep 17 00:00:00 2001 From: Charlie Bailey Date: Wed, 25 Mar 2026 15:46:16 -0700 Subject: [PATCH 3/3] Hash passwords client-side in Login and Signup before sending to backend. --- phd-advisor-frontend/src/components/Login.js | 4 +++- phd-advisor-frontend/src/components/Signup.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/phd-advisor-frontend/src/components/Login.js b/phd-advisor-frontend/src/components/Login.js index 5e17a27d..cb13f96e 100644 --- a/phd-advisor-frontend/src/components/Login.js +++ b/phd-advisor-frontend/src/components/Login.js @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import { Eye, EyeOff, Mail, Lock, ArrowRight, BookOpen, Phone } from 'lucide-react'; import { useAppConfig } from '../contexts/AppConfigContext'; +import { hashPassword } from '../utils/hashPassword'; import '../styles/Login.css'; const Login = ({ onNavigateToSignup, onNavigateToHome }) => { @@ -55,6 +56,7 @@ const Login = ({ onNavigateToSignup, onNavigateToHome }) => { setIsLoading(true); try { + const hashedPassword = await hashPassword(formData.password); const response = await fetch(`${process.env.REACT_APP_API_URL}/auth/login`, { method: 'POST', headers: { @@ -62,7 +64,7 @@ const Login = ({ onNavigateToSignup, onNavigateToHome }) => { }, body: JSON.stringify({ email: formData.email, - password: formData.password + password_hash: hashedPassword }), }); diff --git a/phd-advisor-frontend/src/components/Signup.js b/phd-advisor-frontend/src/components/Signup.js index 4a54242a..d953ba4a 100644 --- a/phd-advisor-frontend/src/components/Signup.js +++ b/phd-advisor-frontend/src/components/Signup.js @@ -1,6 +1,7 @@ import React, { useState } from 'react'; import { Eye, EyeOff, Mail, Lock, User, ArrowRight, BookOpen, Phone, GraduationCap } from 'lucide-react'; import { useAppConfig } from '../contexts/AppConfigContext'; +import { hashPassword } from '../utils/hashPassword'; import '../styles/Signup.css'; const Signup = ({ onNavigateToLogin, onNavigateToHome }) => { @@ -90,6 +91,7 @@ const Signup = ({ onNavigateToLogin, onNavigateToHome }) => { setIsLoading(true); try { + const hashedPassword = await hashPassword(formData.password); const response = await fetch(`${process.env.REACT_APP_API_URL}/auth/signup`, { method: 'POST', headers: { @@ -99,7 +101,7 @@ const Signup = ({ onNavigateToLogin, onNavigateToHome }) => { firstName: formData.firstName, lastName: formData.lastName, email: formData.email, - password: formData.password, + password_hash: hashedPassword, academicStage: formData.academicStage, researchArea: formData.researchArea }),