Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions multi_llm_chatbot_backend/app/api/routes/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
18 changes: 9 additions & 9 deletions multi_llm_chatbot_backend/app/core/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand Down Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions multi_llm_chatbot_backend/app/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
4 changes: 3 additions & 1 deletion phd-advisor-frontend/src/components/Login.js
Original file line number Diff line number Diff line change
@@ -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 }) => {
Expand Down Expand Up @@ -55,14 +56,15 @@ 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: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: formData.email,
password: formData.password
password_hash: hashedPassword
}),
});

Expand Down
4 changes: 3 additions & 1 deletion phd-advisor-frontend/src/components/Signup.js
Original file line number Diff line number Diff line change
@@ -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 }) => {
Expand Down Expand Up @@ -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: {
Expand All @@ -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
}),
Expand Down
14 changes: 14 additions & 0 deletions phd-advisor-frontend/src/utils/hashPassword.js
Original file line number Diff line number Diff line change
@@ -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<string>} 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('');
}
Loading