feat(auth): add profile update and password change endpoints; enhance provider list handling
This commit is contained in:
@@ -42,7 +42,7 @@ def create_app():
|
||||
origins=["http://localhost:3000"], # Frontend URL
|
||||
supports_credentials=True, # Allow cookies
|
||||
allow_headers=["Content-Type", "Authorization"],
|
||||
methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
||||
methods=["GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"],
|
||||
)
|
||||
|
||||
# Initialize JWT manager
|
||||
|
||||
@@ -67,6 +67,13 @@ class User(db.Model):
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Convert user to dictionary."""
|
||||
# Build comprehensive providers list
|
||||
providers = [provider.provider for provider in self.oauth_providers]
|
||||
if self.password_hash:
|
||||
providers.append("password")
|
||||
if self.api_token:
|
||||
providers.append("api_token")
|
||||
|
||||
return {
|
||||
"id": str(self.id),
|
||||
"email": self.email,
|
||||
@@ -76,7 +83,7 @@ class User(db.Model):
|
||||
"is_active": self.is_active,
|
||||
"api_token": self.api_token,
|
||||
"api_token_expires_at": self.api_token_expires_at.isoformat() if self.api_token_expires_at else None,
|
||||
"providers": [provider.provider for provider in self.oauth_providers],
|
||||
"providers": providers,
|
||||
"plan": self.plan.to_dict() if self.plan else None,
|
||||
"credits": self.credits,
|
||||
"created_at": self.created_at.isoformat(),
|
||||
|
||||
@@ -272,3 +272,115 @@ def me():
|
||||
"""Get current user information."""
|
||||
user = get_current_user()
|
||||
return {"user": user}
|
||||
|
||||
|
||||
@bp.route("/profile", methods=["PATCH"])
|
||||
@require_auth
|
||||
def update_profile():
|
||||
"""Update current user profile information."""
|
||||
from flask import request
|
||||
from app.database import db
|
||||
from app.models.user import User
|
||||
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return {"error": "No data provided"}, 400
|
||||
|
||||
user_data = get_current_user()
|
||||
if not user_data:
|
||||
return {"error": "User not authenticated"}, 401
|
||||
|
||||
user = User.query.get(int(user_data["id"]))
|
||||
if not user:
|
||||
return {"error": "User not found"}, 404
|
||||
|
||||
# Update allowed fields
|
||||
if "name" in data:
|
||||
name = data["name"].strip()
|
||||
if not name:
|
||||
return {"error": "Name cannot be empty"}, 400
|
||||
if len(name) > 100:
|
||||
return {"error": "Name too long (max 100 characters)"}, 400
|
||||
user.name = name
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
|
||||
# Return fresh user data from database
|
||||
updated_user = {
|
||||
"id": str(user.id),
|
||||
"email": user.email,
|
||||
"name": user.name,
|
||||
"picture": user.picture,
|
||||
"role": user.role,
|
||||
"is_active": user.is_active,
|
||||
"provider": "password", # This endpoint is only for password users
|
||||
"providers": [p.provider for p in user.oauth_providers],
|
||||
"plan": user.plan.to_dict() if user.plan else None,
|
||||
"credits": user.credits,
|
||||
}
|
||||
|
||||
return {
|
||||
"message": "Profile updated successfully",
|
||||
"user": updated_user
|
||||
}
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return {"error": f"Failed to update profile: {str(e)}"}, 500
|
||||
|
||||
|
||||
@bp.route("/password", methods=["PUT"])
|
||||
@require_auth
|
||||
def change_password():
|
||||
"""Change or set user password."""
|
||||
from flask import request
|
||||
from app.database import db
|
||||
from app.models.user import User
|
||||
from werkzeug.security import check_password_hash
|
||||
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return {"error": "No data provided"}, 400
|
||||
|
||||
user_data = get_current_user()
|
||||
if not user_data:
|
||||
return {"error": "User not authenticated"}, 401
|
||||
|
||||
user = User.query.get(int(user_data["id"]))
|
||||
if not user:
|
||||
return {"error": "User not found"}, 404
|
||||
|
||||
new_password = data.get("new_password")
|
||||
current_password = data.get("current_password")
|
||||
|
||||
if not new_password:
|
||||
return {"error": "New password is required"}, 400
|
||||
|
||||
# Password validation
|
||||
if len(new_password) < 6:
|
||||
return {"error": "Password must be at least 6 characters long"}, 400
|
||||
|
||||
# Check authentication method: if user logged in via password, require current password
|
||||
# If user logged in via OAuth, they can change password without current password
|
||||
current_auth_method = user_data.get("provider", "unknown")
|
||||
|
||||
if user.password_hash and current_auth_method == "password":
|
||||
# User has a password AND logged in via password, require current password for verification
|
||||
if not current_password:
|
||||
return {"error": "Current password is required to change password"}, 400
|
||||
|
||||
if not check_password_hash(user.password_hash, current_password):
|
||||
return {"error": "Current password is incorrect"}, 400
|
||||
# If user logged in via OAuth (google, github, etc.), they can change password without current password
|
||||
|
||||
# Set the new password
|
||||
try:
|
||||
user.set_password(new_password)
|
||||
db.session.commit()
|
||||
|
||||
return {
|
||||
"message": "Password updated successfully"
|
||||
}
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return {"error": f"Failed to update password: {str(e)}"}, 500
|
||||
|
||||
@@ -56,6 +56,13 @@ def get_user_from_api_token() -> dict[str, Any] | None:
|
||||
|
||||
user = User.find_by_api_token(api_token)
|
||||
if user and user.is_active:
|
||||
# Build comprehensive providers list
|
||||
providers = [p.provider for p in user.oauth_providers]
|
||||
if user.password_hash:
|
||||
providers.append("password")
|
||||
if user.api_token:
|
||||
providers.append("api_token")
|
||||
|
||||
return {
|
||||
"id": str(user.id),
|
||||
"email": user.email,
|
||||
@@ -64,8 +71,7 @@ def get_user_from_api_token() -> dict[str, Any] | None:
|
||||
"role": user.role,
|
||||
"is_active": user.is_active,
|
||||
"provider": "api_token",
|
||||
"providers": [p.provider for p in user.oauth_providers]
|
||||
+ ["api_token"],
|
||||
"providers": providers,
|
||||
"plan": user.plan.to_dict() if user.plan else None,
|
||||
"credits": user.credits,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user