diff --git a/backend/app/routers/auth.py b/backend/app/routers/auth.py index 1a2c548..7a5b77f 100644 --- a/backend/app/routers/auth.py +++ b/backend/app/routers/auth.py @@ -1,8 +1,6 @@ -import hashlib import secrets from datetime import datetime, timedelta -import httpx from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks, status, Request from sqlalchemy.orm import Session @@ -22,25 +20,6 @@ from app.utils.auth import ( router = APIRouter() -def _check_pwned_password(password: str) -> int | None: - """Check if password appears in Have I Been Pwned breach database. - Uses k-anonymity: only first 5 chars of SHA-1 hash are sent. - Returns breach count, 0 if clean, or None if check failed.""" - try: - sha1 = hashlib.sha1(password.encode("utf-8")).hexdigest().upper() - prefix, suffix = sha1[:5], sha1[5:] - resp = httpx.get(f"https://api.pwnedpasswords.com/range/{prefix}", timeout=3) - if resp.status_code != 200: - return None - for line in resp.text.splitlines(): - hash_suffix, count = line.split(":") - if hash_suffix == suffix: - return int(count) - return 0 - except Exception: - return None # fail open — don't block registration if API is down - - def _check_login_rate_limit(client_ip: str): """Rate limit: max 10 login attempts per IP per 15 min, persisted in Redis.""" try: @@ -129,15 +108,6 @@ async def register(user_data: UserCreate, background_tasks: BackgroundTasks, db: if len(user_data.password) < 8: raise HTTPException(status_code=400, detail="Password must be at least 8 characters") - # Check breached passwords (warn, don't block) - pwned_count = _check_pwned_password(user_data.password) - password_warning = None - if pwned_count and pwned_count > 0: - password_warning = ( - f"This password has appeared in {pwned_count:,} data breach{'es' if pwned_count > 1 else ''}. " - "Consider changing it to something unique." - ) - email_normalized = user_data.email.lower().strip() existing = db.query(User).filter(User.email == email_normalized).first() if existing: @@ -166,17 +136,11 @@ async def register(user_data: UserCreate, background_tasks: BackgroundTasks, db: if is_first_user: # Auto-verified admin — log them in immediately access_token = create_access_token(data={"sub": user.email}) - resp = {"access_token": access_token, "token_type": "bearer"} - if password_warning: - resp["password_warning"] = password_warning - return resp + return {"access_token": access_token, "token_type": "bearer"} # Regular user — must verify email before logging in background_tasks.add_task(email_service.send_verification_email, user.email, user.name, token) - resp = {"requires_verification": True, "message": "Account created. Please check your email to verify your account before logging in."} - if password_warning: - resp["password_warning"] = password_warning - return resp + return {"requires_verification": True, "message": "Account created. Please check your email to verify your account before logging in."} @router.post("/login", response_model=Token) @@ -289,17 +253,10 @@ def reset_password(data: ResetPasswordRequest, db: Session = Depends(get_db)): if not user: raise HTTPException(status_code=404, detail="User not found") - pwned_count = _check_pwned_password(data.new_password) user.hashed_password = get_password_hash(data.new_password) record.used = True db.commit() - resp = {"message": "Password reset successfully. You can now log in."} - if pwned_count and pwned_count > 0: - resp["password_warning"] = ( - f"Warning: this password has appeared in {pwned_count:,} data breach{'es' if pwned_count > 1 else ''}. " - "Consider changing it to something more unique." - ) - return resp + return {"message": "Password reset successfully. You can now log in."} @router.get("/me/settings") @@ -339,7 +296,6 @@ def get_me(current_user: User = Depends(get_current_user)): @router.put("/me") def update_me(data: UserUpdateMe, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)): - password_warning = None if data.new_password: if not data.current_password: raise HTTPException(status_code=400, detail="Current password required to set a new one") @@ -347,12 +303,6 @@ def update_me(data: UserUpdateMe, db: Session = Depends(get_db), current_user: U raise HTTPException(status_code=400, detail="Current password is incorrect") if len(data.new_password) < 8: raise HTTPException(status_code=400, detail="New password must be at least 8 characters") - pwned_count = _check_pwned_password(data.new_password) - if pwned_count and pwned_count > 0: - password_warning = ( - f"This password has appeared in {pwned_count:,} data breach{'es' if pwned_count > 1 else ''}. " - "Consider changing it to something more unique." - ) current_user.hashed_password = get_password_hash(data.new_password) if data.name: @@ -360,10 +310,7 @@ def update_me(data: UserUpdateMe, db: Session = Depends(get_db), current_user: U db.commit() db.refresh(current_user) - resp = { + return { "id": current_user.id, "email": current_user.email, "name": current_user.name, "role": current_user.role, } - if password_warning: - resp["password_warning"] = password_warning - return resp diff --git a/frontend/src/pages/AccountPage.jsx b/frontend/src/pages/AccountPage.jsx index 685ced5..72b235f 100644 --- a/frontend/src/pages/AccountPage.jsx +++ b/frontend/src/pages/AccountPage.jsx @@ -41,10 +41,7 @@ export default function AccountPage() { return } - const res = await api.put('/auth/me', payload) - if (res.data.password_warning) { - setError(res.data.password_warning) - } + await api.put('/auth/me', payload) setSuccess('Account updated successfully') setCurrentPassword('') setNewPassword('') diff --git a/frontend/src/pages/LandingPage.jsx b/frontend/src/pages/LandingPage.jsx index 9d836c7..a767e8a 100644 --- a/frontend/src/pages/LandingPage.jsx +++ b/frontend/src/pages/LandingPage.jsx @@ -178,7 +178,6 @@ function AuthModal({ mode, onClose, onSwitch }) { const [resendSent, setResendSent] = useState(false) const [resending, setResending] = useState(false) const [registered, setRegistered] = useState(false) - const [passwordWarning, setPasswordWarning] = useState('') const reset = () => { setError(''); setUnverified(false); setResendSent(false); setRegistered(false) } const switchMode = (m) => { reset(); setName(''); setEmail(''); setPassword(''); onSwitch(m) } @@ -203,9 +202,6 @@ function AuthModal({ mode, onClose, onSwitch }) { setLoading(true) try { const res = await api.post('/auth/register', { email, password, name, turnstile_token: turnstileToken || null }) - if (res.data.password_warning) { - setPasswordWarning(res.data.password_warning) - } if (res.data.requires_verification) { setRegistered(true) } else { @@ -266,11 +262,6 @@ function AuthModal({ mode, onClose, onSwitch }) {

We sent a verification link to {email}. Click it to activate your account.

- {passwordWarning && ( -
- ⚠️ {passwordWarning} -
- )} @@ -468,7 +459,7 @@ export default function LandingPage() { ))} Contact - AI Scribe + AI Scribe diff --git a/frontend/src/pages/RegisterPage.jsx b/frontend/src/pages/RegisterPage.jsx index 4c28a00..8edc02f 100644 --- a/frontend/src/pages/RegisterPage.jsx +++ b/frontend/src/pages/RegisterPage.jsx @@ -36,7 +36,6 @@ export default function RegisterPage() { const [error, setError] = useState('') const [loading, setLoading] = useState(false) const [done, setDone] = useState(false) - const [passwordWarning, setPasswordWarning] = useState('') const [turnstileToken, setTurnstileToken] = useState('') const { loginWithToken } = useAuth() @@ -46,9 +45,6 @@ export default function RegisterPage() { setLoading(true) try { const res = await api.post('/auth/register', { email, password, name, turnstile_token: turnstileToken || null }) - if (res.data.password_warning) { - setPasswordWarning(res.data.password_warning) - } if (res.data.requires_verification) { setDone(true) } else { @@ -80,11 +76,6 @@ export default function RegisterPage() {

Click the link in the email to activate your account, then you can log in.

- {passwordWarning && ( -
- ⚠️ {passwordWarning} -
- )} Go to Login
Wrong email? diff --git a/frontend/src/pages/ResetPasswordPage.jsx b/frontend/src/pages/ResetPasswordPage.jsx index 8b36197..8010263 100644 --- a/frontend/src/pages/ResetPasswordPage.jsx +++ b/frontend/src/pages/ResetPasswordPage.jsx @@ -19,12 +19,9 @@ export default function ResetPasswordPage() { if (password.length < 8) { setError('Password must be at least 8 characters'); return } setLoading(true) try { - const res = await api.post('/auth/reset-password', { token, new_password: password }) - if (res.data.password_warning) { - setError(res.data.password_warning) - } + await api.post('/auth/reset-password', { token, new_password: password }) setSuccess(true) - setTimeout(() => navigate('/login'), 3500) + setTimeout(() => navigate('/login'), 2500) } catch (err) { setError(err.response?.data?.detail || 'Reset failed. The link may have expired.') } finally { diff --git a/frontend/src/pages/SettingsPage.jsx b/frontend/src/pages/SettingsPage.jsx index 78a5a2b..82dcfcd 100644 --- a/frontend/src/pages/SettingsPage.jsx +++ b/frontend/src/pages/SettingsPage.jsx @@ -33,10 +33,7 @@ function ProfileSection({ user }) { if (!Object.keys(payload).length) return setError('No changes to save') setLoading(true) try { - const res = await api.put('/auth/me', payload) - if (res.data.password_warning) { - setError(res.data.password_warning) - } + await api.put('/auth/me', payload) setSuccess('Saved successfully') setCurrentPassword(''); setNewPassword(''); setConfirmPassword('') if (payload.name) setTimeout(() => window.location.reload(), 800)