auth email/password
This commit is contained in:
@@ -1,31 +1,54 @@
|
||||
"""Authentication routes."""
|
||||
|
||||
from flask import Blueprint, url_for
|
||||
from flask import Blueprint, jsonify, url_for
|
||||
from flask_jwt_extended import create_access_token, get_jwt_identity, jwt_required
|
||||
|
||||
from app.services.auth_service import AuthService
|
||||
from app import auth_service
|
||||
from app.services.decorators import get_current_user
|
||||
|
||||
bp = Blueprint("auth", __name__)
|
||||
auth_service = AuthService()
|
||||
|
||||
|
||||
@bp.route("/login")
|
||||
def login() -> dict[str, str]:
|
||||
"""Initiate Google OAuth login."""
|
||||
redirect_uri = url_for("auth.callback", _external=True)
|
||||
login_url = auth_service.get_login_url(redirect_uri)
|
||||
return {"login_url": login_url}
|
||||
@bp.route("/login/<provider>")
|
||||
def login_oauth(provider):
|
||||
"""Initiate OAuth login for specified provider."""
|
||||
redirect_uri = url_for("auth.callback", provider=provider, _external=True)
|
||||
return auth_service.redirect_to_login(provider, redirect_uri)
|
||||
|
||||
|
||||
@bp.route("/callback")
|
||||
def callback():
|
||||
"""Handle OAuth callback from Google."""
|
||||
@bp.route("/callback/<provider>")
|
||||
def callback(provider):
|
||||
"""Handle OAuth callback from specified provider."""
|
||||
try:
|
||||
user_data, response = auth_service.handle_callback()
|
||||
return response
|
||||
return auth_service.handle_callback(provider)
|
||||
except Exception as e:
|
||||
return {"error": str(e)}, 400
|
||||
|
||||
|
||||
@bp.route("/providers")
|
||||
def providers():
|
||||
"""Get list of available OAuth providers."""
|
||||
return {"providers": auth_service.get_available_providers()}
|
||||
|
||||
|
||||
@bp.route("/login", methods=["POST"])
|
||||
def login():
|
||||
"""Login user with email and password."""
|
||||
from flask import request
|
||||
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return {"error": "No data provided"}, 400
|
||||
|
||||
email = data.get("email")
|
||||
password = data.get("password")
|
||||
|
||||
if not email or not password:
|
||||
return {"error": "Email and password are required"}, 400
|
||||
|
||||
return auth_service.login_with_password(email, password)
|
||||
|
||||
|
||||
@bp.route("/logout")
|
||||
def logout():
|
||||
"""Logout current user."""
|
||||
@@ -33,20 +56,182 @@ def logout():
|
||||
|
||||
|
||||
@bp.route("/me")
|
||||
def me() -> dict[str, str] | tuple[dict[str, str], int]:
|
||||
@jwt_required()
|
||||
def me():
|
||||
"""Get current user information."""
|
||||
user = auth_service.get_current_user()
|
||||
if not user:
|
||||
return {"error": "Not authenticated"}, 401
|
||||
|
||||
user = get_current_user()
|
||||
return {"user": user}
|
||||
|
||||
|
||||
@bp.route("/refresh")
|
||||
@bp.route("/refresh", methods=["POST"])
|
||||
@jwt_required(refresh=True)
|
||||
def refresh():
|
||||
"""Refresh access token using refresh token."""
|
||||
response = auth_service.refresh_tokens()
|
||||
if not response:
|
||||
return {"error": "Invalid or expired refresh token"}, 401
|
||||
current_user_id = get_jwt_identity()
|
||||
|
||||
# Create new access token
|
||||
new_access_token = create_access_token(identity=current_user_id)
|
||||
|
||||
response = jsonify({"message": "Token refreshed"})
|
||||
|
||||
# Set new access token cookie
|
||||
from flask_jwt_extended import set_access_cookies
|
||||
set_access_cookies(response, new_access_token)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@bp.route("/link/<provider>")
|
||||
@jwt_required()
|
||||
def link_provider(provider):
|
||||
"""Link a new OAuth provider to current user account."""
|
||||
redirect_uri = url_for("auth.link_callback", provider=provider, _external=True)
|
||||
return auth_service.redirect_to_login(provider, redirect_uri)
|
||||
|
||||
|
||||
@bp.route("/link/callback/<provider>")
|
||||
@jwt_required()
|
||||
def link_callback(provider):
|
||||
"""Handle OAuth callback for linking new provider."""
|
||||
try:
|
||||
current_user_id = get_jwt_identity()
|
||||
if not current_user_id:
|
||||
return {"error": "User not authenticated"}, 401
|
||||
|
||||
# Get current user from database
|
||||
from app.models.user import User
|
||||
user = User.query.get(current_user_id)
|
||||
if not user:
|
||||
return {"error": "User not found"}, 404
|
||||
|
||||
# Process OAuth callback but link to existing user
|
||||
from app.services.oauth_providers.registry import OAuthProviderRegistry
|
||||
from authlib.integrations.flask_client import OAuth
|
||||
|
||||
oauth = OAuth()
|
||||
registry = OAuthProviderRegistry(oauth)
|
||||
oauth_provider = registry.get_provider(provider)
|
||||
|
||||
if not oauth_provider:
|
||||
return {"error": f"OAuth provider '{provider}' not configured"}, 400
|
||||
|
||||
token = oauth_provider.exchange_code_for_token(None, None)
|
||||
raw_user_info = oauth_provider.get_user_info(token)
|
||||
provider_data = oauth_provider.normalize_user_data(raw_user_info)
|
||||
|
||||
if not provider_data.get("id"):
|
||||
return {"error": "Failed to get user information from provider"}, 400
|
||||
|
||||
# Check if this provider is already linked to another user
|
||||
from app.models.user_oauth import UserOAuth
|
||||
existing_provider = UserOAuth.find_by_provider_and_id(
|
||||
provider, provider_data["id"]
|
||||
)
|
||||
|
||||
if existing_provider and existing_provider.user_id != user.id:
|
||||
return {"error": "This provider account is already linked to another user"}, 409
|
||||
|
||||
# Link the provider to current user
|
||||
UserOAuth.create_or_update(
|
||||
user_id=user.id,
|
||||
provider=provider,
|
||||
provider_id=provider_data["id"],
|
||||
email=provider_data["email"],
|
||||
name=provider_data["name"],
|
||||
picture=provider_data.get("picture")
|
||||
)
|
||||
|
||||
return {"message": f"{provider.title()} account linked successfully"}
|
||||
|
||||
except Exception as e:
|
||||
return {"error": str(e)}, 400
|
||||
|
||||
|
||||
@bp.route("/unlink/<provider>", methods=["DELETE"])
|
||||
@jwt_required()
|
||||
def unlink_provider(provider):
|
||||
"""Unlink an OAuth provider from current user account."""
|
||||
try:
|
||||
current_user_id = get_jwt_identity()
|
||||
if not current_user_id:
|
||||
return {"error": "User not authenticated"}, 401
|
||||
|
||||
from app.models.user import User
|
||||
from app.models.user_oauth import UserOAuth
|
||||
from app.database import db
|
||||
|
||||
user = User.query.get(current_user_id)
|
||||
if not user:
|
||||
return {"error": "User not found"}, 404
|
||||
|
||||
# Check if user has more than one provider (prevent locking out)
|
||||
if len(user.oauth_providers) <= 1:
|
||||
return {"error": "Cannot unlink last authentication provider"}, 400
|
||||
|
||||
# Find and remove the provider
|
||||
oauth_provider = user.get_provider(provider)
|
||||
if not oauth_provider:
|
||||
return {"error": f"Provider '{provider}' not linked to this account"}, 404
|
||||
|
||||
db.session.delete(oauth_provider)
|
||||
db.session.commit()
|
||||
|
||||
return {"message": f"{provider.title()} account unlinked successfully"}
|
||||
|
||||
except Exception as e:
|
||||
return {"error": str(e)}, 400
|
||||
|
||||
|
||||
@bp.route("/register", methods=["POST"])
|
||||
def register():
|
||||
"""Register new user with email and password."""
|
||||
from flask import request
|
||||
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return {"error": "No data provided"}, 400
|
||||
|
||||
email = data.get("email")
|
||||
password = data.get("password")
|
||||
name = data.get("name")
|
||||
|
||||
if not email or not password or not name:
|
||||
return {"error": "Email, password, and name are required"}, 400
|
||||
|
||||
# Basic email validation
|
||||
if "@" not in email or "." not in email:
|
||||
return {"error": "Invalid email format"}, 400
|
||||
|
||||
# Basic password validation
|
||||
if len(password) < 6:
|
||||
return {"error": "Password must be at least 6 characters long"}, 400
|
||||
|
||||
return auth_service.register_with_password(email, password, name)
|
||||
|
||||
|
||||
@bp.route("/regenerate-api-token", methods=["POST"])
|
||||
@jwt_required()
|
||||
def regenerate_api_token():
|
||||
"""Regenerate API token for current user."""
|
||||
current_user_id = get_jwt_identity()
|
||||
if not current_user_id:
|
||||
return {"error": "User not authenticated"}, 401
|
||||
|
||||
from app.models.user import User
|
||||
from app.database import db
|
||||
|
||||
user = User.query.get(current_user_id)
|
||||
if not user:
|
||||
return {"error": "User not found"}, 404
|
||||
|
||||
# Generate new API token
|
||||
new_token = user.generate_api_token()
|
||||
db.session.commit()
|
||||
|
||||
return {
|
||||
"message": "API token regenerated successfully",
|
||||
"api_token": new_token,
|
||||
"expires_at": user.api_token_expires_at.isoformat() if user.api_token_expires_at else None
|
||||
}
|
||||
|
||||
|
||||
return response
|
||||
Reference in New Issue
Block a user