Basic frontend
This commit is contained in:
@@ -3,12 +3,14 @@ from fastapi.security import OAuth2PasswordRequestForm
|
||||
from sqlalchemy.orm import Session
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List
|
||||
import uuid
|
||||
|
||||
from ...core.database import get_db
|
||||
from ...core.security import verify_password, get_password_hash, create_access_token
|
||||
from ...models.models import User, UserRole
|
||||
from ...models.models import User, UserRole, PasswordResetToken
|
||||
from ...schemas import (
|
||||
UserCreate, UserResponse, Token, LoginRequest, MessageResponse
|
||||
UserCreate, UserResponse, Token, LoginRequest, MessageResponse,
|
||||
ForgotPasswordRequest, ResetPasswordRequest
|
||||
)
|
||||
from ...services.email_service import email_service
|
||||
|
||||
@@ -126,3 +128,92 @@ async def login_json(
|
||||
"access_token": access_token,
|
||||
"token_type": "bearer"
|
||||
}
|
||||
|
||||
|
||||
@router.post("/forgot-password", response_model=MessageResponse)
|
||||
async def forgot_password(
|
||||
request: ForgotPasswordRequest,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Request password reset for a user"""
|
||||
# Find user by email
|
||||
user = db.query(User).filter(User.email == request.email).first()
|
||||
|
||||
if not user or not user.is_active:
|
||||
# Don't reveal if email exists or not for security
|
||||
return {"message": "If an account with this email exists, a password reset link has been sent."}
|
||||
|
||||
# Invalidate any existing reset tokens for this user
|
||||
db.query(PasswordResetToken).filter(
|
||||
PasswordResetToken.user_id == user.id,
|
||||
PasswordResetToken.used == False,
|
||||
PasswordResetToken.expires_at > datetime.utcnow()
|
||||
).update({"used": True})
|
||||
|
||||
# Generate new reset token
|
||||
reset_token = str(uuid.uuid4())
|
||||
expires_at = datetime.utcnow() + timedelta(hours=1) # Token expires in 1 hour
|
||||
|
||||
# Create password reset token
|
||||
db_token = PasswordResetToken(
|
||||
user_id=user.id,
|
||||
token=reset_token,
|
||||
expires_at=expires_at,
|
||||
used=False
|
||||
)
|
||||
|
||||
db.add(db_token)
|
||||
db.commit()
|
||||
|
||||
# Send password reset email (non-blocking, ignore errors)
|
||||
try:
|
||||
await email_service.send_password_reset_email(
|
||||
to_email=user.email,
|
||||
first_name=user.first_name,
|
||||
reset_token=reset_token
|
||||
)
|
||||
except Exception as e:
|
||||
# Log error but don't fail the request
|
||||
print(f"Failed to send password reset email: {e}")
|
||||
|
||||
return {"message": "If an account with this email exists, a password reset link has been sent."}
|
||||
|
||||
|
||||
@router.post("/reset-password", response_model=MessageResponse)
|
||||
async def reset_password(
|
||||
request: ResetPasswordRequest,
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Reset password using reset token"""
|
||||
# Find valid reset token
|
||||
reset_token = db.query(PasswordResetToken).filter(
|
||||
PasswordResetToken.token == request.token,
|
||||
PasswordResetToken.used == False,
|
||||
PasswordResetToken.expires_at > datetime.utcnow()
|
||||
).first()
|
||||
|
||||
if not reset_token:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid or expired reset token"
|
||||
)
|
||||
|
||||
# Get the user
|
||||
user = db.query(User).filter(User.id == reset_token.user_id).first()
|
||||
if not user or not user.is_active:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid reset token"
|
||||
)
|
||||
|
||||
# Update password
|
||||
hashed_password = get_password_hash(request.new_password)
|
||||
user.hashed_password = hashed_password
|
||||
user.updated_at = datetime.utcnow()
|
||||
|
||||
# Mark token as used
|
||||
reset_token.used = True
|
||||
|
||||
db.commit()
|
||||
|
||||
return {"message": "Password has been reset successfully. You can now log in with your new password."}
|
||||
|
||||
Reference in New Issue
Block a user