forked from andika/membership-be
Email SMTP Fix
This commit is contained in:
175
server.py
175
server.py
@@ -20,9 +20,18 @@ from auth import (
|
||||
verify_password,
|
||||
create_access_token,
|
||||
get_current_user,
|
||||
get_current_admin_user
|
||||
get_current_admin_user,
|
||||
get_active_member,
|
||||
create_password_reset_token,
|
||||
verify_reset_token
|
||||
)
|
||||
from email_service import (
|
||||
send_verification_email,
|
||||
send_approval_notification,
|
||||
send_payment_prompt_email,
|
||||
send_password_reset_email,
|
||||
send_admin_password_reset_email
|
||||
)
|
||||
from email_service import send_verification_email, send_approval_notification, send_payment_prompt_email
|
||||
from payment_service import create_checkout_session, verify_webhook_signature, get_subscription_end_date
|
||||
|
||||
# Load environment variables
|
||||
@@ -125,6 +134,20 @@ class LoginResponse(BaseModel):
|
||||
token_type: str
|
||||
user: dict
|
||||
|
||||
class ForgotPasswordRequest(BaseModel):
|
||||
email: EmailStr
|
||||
|
||||
class ResetPasswordRequest(BaseModel):
|
||||
token: str
|
||||
new_password: str = Field(min_length=6)
|
||||
|
||||
class ChangePasswordRequest(BaseModel):
|
||||
current_password: str
|
||||
new_password: str = Field(min_length=6)
|
||||
|
||||
class AdminPasswordUpdateRequest(BaseModel):
|
||||
force_change: bool = True
|
||||
|
||||
class UserResponse(BaseModel):
|
||||
id: str
|
||||
email: str
|
||||
@@ -314,9 +337,32 @@ async def verify_email(token: str, db: Session = Depends(get_db)):
|
||||
db.refresh(user)
|
||||
|
||||
logger.info(f"Email verified for user: {user.email}")
|
||||
|
||||
|
||||
return {"message": "Email verified successfully", "status": user.status.value}
|
||||
|
||||
@api_router.post("/auth/resend-verification-email")
|
||||
async def resend_verification_email(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""User requests to resend their verification email"""
|
||||
|
||||
# Check if email already verified
|
||||
if current_user.email_verified:
|
||||
raise HTTPException(status_code=400, detail="Email is already verified")
|
||||
|
||||
# Generate new token
|
||||
verification_token = secrets.token_urlsafe(32)
|
||||
current_user.email_verification_token = verification_token
|
||||
db.commit()
|
||||
|
||||
# Send verification email
|
||||
await send_verification_email(current_user.email, verification_token)
|
||||
|
||||
logger.info(f"Verification email resent to: {current_user.email}")
|
||||
|
||||
return {"message": "Verification email has been resent. Please check your inbox."}
|
||||
|
||||
@api_router.post("/auth/login", response_model=LoginResponse)
|
||||
async def login(request: LoginRequest, db: Session = Depends(get_db)):
|
||||
user = db.query(User).filter(User.email == request.email).first()
|
||||
@@ -338,10 +384,60 @@ async def login(request: LoginRequest, db: Session = Depends(get_db)):
|
||||
"first_name": user.first_name,
|
||||
"last_name": user.last_name,
|
||||
"status": user.status.value,
|
||||
"role": user.role.value
|
||||
"role": user.role.value,
|
||||
"force_password_change": user.force_password_change
|
||||
}
|
||||
}
|
||||
|
||||
@api_router.post("/auth/forgot-password")
|
||||
async def forgot_password(request: ForgotPasswordRequest, db: Session = Depends(get_db)):
|
||||
"""Request password reset - sends email with reset link"""
|
||||
user = db.query(User).filter(User.email == request.email).first()
|
||||
|
||||
# Always return success (security: don't reveal if email exists)
|
||||
if user:
|
||||
token = create_password_reset_token(user, db)
|
||||
reset_url = f"{os.getenv('FRONTEND_URL')}/reset-password?token={token}"
|
||||
|
||||
await send_password_reset_email(user.email, user.first_name, reset_url)
|
||||
|
||||
return {"message": "If email exists, reset link has been sent"}
|
||||
|
||||
@api_router.post("/auth/reset-password")
|
||||
async def reset_password(request: ResetPasswordRequest, db: Session = Depends(get_db)):
|
||||
"""Complete password reset using token"""
|
||||
user = verify_reset_token(request.token, db)
|
||||
|
||||
if not user:
|
||||
raise HTTPException(status_code=400, detail="Invalid or expired reset token")
|
||||
|
||||
# Update password
|
||||
user.password_hash = get_password_hash(request.new_password)
|
||||
user.password_reset_token = None
|
||||
user.password_reset_expires = None
|
||||
user.force_password_change = False # Reset flag if it was set
|
||||
db.commit()
|
||||
|
||||
return {"message": "Password reset successful"}
|
||||
|
||||
@api_router.put("/users/change-password")
|
||||
async def change_password(
|
||||
request: ChangePasswordRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""User changes their own password"""
|
||||
# Verify current password
|
||||
if not verify_password(request.current_password, current_user.password_hash):
|
||||
raise HTTPException(status_code=400, detail="Current password is incorrect")
|
||||
|
||||
# Update password
|
||||
current_user.password_hash = get_password_hash(request.new_password)
|
||||
current_user.force_password_change = False # Clear flag if set
|
||||
db.commit()
|
||||
|
||||
return {"message": "Password changed successfully"}
|
||||
|
||||
@api_router.get("/auth/me", response_model=UserResponse)
|
||||
async def get_me(current_user: User = Depends(get_current_user)):
|
||||
return UserResponse(
|
||||
@@ -412,6 +508,7 @@ async def update_profile(
|
||||
# Event Routes
|
||||
@api_router.get("/events", response_model=List[EventResponse])
|
||||
async def get_events(
|
||||
current_user: User = Depends(get_active_member),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
# Get published events for all users
|
||||
@@ -445,6 +542,7 @@ async def get_events(
|
||||
@api_router.get("/events/{event_id}", response_model=EventResponse)
|
||||
async def get_event(
|
||||
event_id: str,
|
||||
current_user: User = Depends(get_active_member),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
event = db.query(Event).filter(Event.id == event_id).first()
|
||||
@@ -478,7 +576,7 @@ async def get_event(
|
||||
async def rsvp_to_event(
|
||||
event_id: str,
|
||||
request: RSVPRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
current_user: User = Depends(get_active_member),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
event = db.query(Event).filter(Event.id == event_id).first()
|
||||
@@ -743,6 +841,73 @@ async def activate_payment_manually(
|
||||
"subscription_id": str(subscription.id)
|
||||
}
|
||||
|
||||
@api_router.put("/admin/users/{user_id}/reset-password")
|
||||
async def admin_reset_user_password(
|
||||
user_id: str,
|
||||
request: AdminPasswordUpdateRequest,
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Admin resets user password - generates temp password and emails it"""
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
# Generate random temporary password
|
||||
temp_password = secrets.token_urlsafe(12)
|
||||
|
||||
# Update user
|
||||
user.password_hash = get_password_hash(temp_password)
|
||||
user.force_password_change = request.force_change
|
||||
db.commit()
|
||||
|
||||
# Email user the temporary password
|
||||
await send_admin_password_reset_email(
|
||||
user.email,
|
||||
user.first_name,
|
||||
temp_password,
|
||||
request.force_change
|
||||
)
|
||||
|
||||
# Log admin action
|
||||
logger.info(
|
||||
f"Admin {current_user.email} reset password for user {user.email} "
|
||||
f"(force_change={request.force_change})"
|
||||
)
|
||||
|
||||
return {"message": f"Password reset for {user.email}. Temporary password emailed."}
|
||||
|
||||
@api_router.post("/admin/users/{user_id}/resend-verification")
|
||||
async def admin_resend_verification(
|
||||
user_id: str,
|
||||
current_user: User = Depends(get_current_admin_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Admin resends verification email for any user"""
|
||||
|
||||
user = db.query(User).filter(User.id == user_id).first()
|
||||
if not user:
|
||||
raise HTTPException(status_code=404, detail="User not found")
|
||||
|
||||
# Check if email already verified
|
||||
if user.email_verified:
|
||||
raise HTTPException(status_code=400, detail="User's email is already verified")
|
||||
|
||||
# Generate new token
|
||||
verification_token = secrets.token_urlsafe(32)
|
||||
user.email_verification_token = verification_token
|
||||
db.commit()
|
||||
|
||||
# Send verification email
|
||||
await send_verification_email(user.email, verification_token)
|
||||
|
||||
# Log admin action
|
||||
logger.info(
|
||||
f"Admin {current_user.email} resent verification email to user {user.email}"
|
||||
)
|
||||
|
||||
return {"message": f"Verification email resent to {user.email}"}
|
||||
|
||||
@api_router.post("/admin/events", response_model=EventResponse)
|
||||
async def create_event(
|
||||
request: EventCreate,
|
||||
|
||||
Reference in New Issue
Block a user