- Modernized authentication with the introduction of TenantProject
- Created a base mail template - Adapt and improve document API to usage of catalogs and processors - Adapt eveai_sync to new authentication mechanism and usage of catalogs and processors
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
import traceback
|
||||
|
||||
from flask import Flask, jsonify, request
|
||||
from flask_jwt_extended import get_jwt_identity, verify_jwt_in_request
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from werkzeug.exceptions import HTTPException
|
||||
|
||||
from common.extensions import db, api_rest, jwt, minio_client, simple_encryption
|
||||
import os
|
||||
import logging.config
|
||||
@@ -45,10 +50,8 @@ def create_app(config_file=None):
|
||||
# Register Blueprints
|
||||
register_blueprints(app)
|
||||
|
||||
# Error handler for the API
|
||||
@app.errorhandler(EveAIException)
|
||||
def handle_eveai_exception(error):
|
||||
return {'message': str(error)}, error.status_code
|
||||
# Register Error Handlers
|
||||
register_error_handlers(app)
|
||||
|
||||
@app.before_request
|
||||
def before_request():
|
||||
@@ -91,3 +94,61 @@ def register_blueprints(app):
|
||||
from .views.healthz_views import healthz_bp
|
||||
app.register_blueprint(healthz_bp)
|
||||
|
||||
|
||||
def register_error_handlers(app):
|
||||
@app.errorhandler(Exception)
|
||||
def handle_exception(e):
|
||||
"""Handle all unhandled exceptions with detailed error responses"""
|
||||
# Get the current exception info
|
||||
exc_info = traceback.format_exc()
|
||||
|
||||
# Log the full exception details
|
||||
app.logger.error(f"Unhandled exception: {str(e)}\n{exc_info}")
|
||||
|
||||
# Start with a default error response
|
||||
response = {
|
||||
"error": "Internal Server Error",
|
||||
"message": str(e),
|
||||
"type": e.__class__.__name__
|
||||
}
|
||||
|
||||
status_code = 500
|
||||
|
||||
# Handle specific types of exceptions
|
||||
if isinstance(e, HTTPException):
|
||||
status_code = e.code
|
||||
response["error"] = e.name
|
||||
|
||||
elif isinstance(e, SQLAlchemyError):
|
||||
response["error"] = "Database Error"
|
||||
response["details"] = str(e.__cause__ or e)
|
||||
|
||||
elif isinstance(e, ValueError):
|
||||
status_code = 400
|
||||
response["error"] = "Invalid Input"
|
||||
|
||||
# In development, include additional debug information
|
||||
if app.debug:
|
||||
response["debug"] = {
|
||||
"exception": exc_info,
|
||||
"class": e.__class__.__name__,
|
||||
"module": e.__class__.__module__
|
||||
}
|
||||
|
||||
return jsonify(response), status_code
|
||||
|
||||
@app.errorhandler(404)
|
||||
def not_found_error(e):
|
||||
return jsonify({
|
||||
"error": "Not Found",
|
||||
"message": str(e),
|
||||
"type": "NotFoundError"
|
||||
}), 404
|
||||
|
||||
@app.errorhandler(400)
|
||||
def bad_request_error(e):
|
||||
return jsonify({
|
||||
"error": "Bad Request",
|
||||
"message": str(e),
|
||||
"type": "BadRequestError"
|
||||
}), 400
|
||||
|
||||
@@ -2,7 +2,7 @@ from datetime import timedelta
|
||||
|
||||
from flask_restx import Namespace, Resource, fields
|
||||
from flask_jwt_extended import create_access_token
|
||||
from common.models.user import Tenant
|
||||
from common.models.user import Tenant, TenantProject
|
||||
from common.extensions import simple_encryption
|
||||
from flask import current_app, request
|
||||
|
||||
@@ -30,8 +30,9 @@ class Token(Resource):
|
||||
"""
|
||||
Get JWT token
|
||||
"""
|
||||
current_app.logger.debug(f'Token Requested {auth_ns.payload}')
|
||||
try:
|
||||
tenant_id = auth_ns.payload['tenant_id']
|
||||
tenant_id = int(auth_ns.payload['tenant_id'])
|
||||
api_key = auth_ns.payload['api_key']
|
||||
except KeyError as e:
|
||||
current_app.logger.error(f"Missing required field: {e}")
|
||||
@@ -41,18 +42,34 @@ class Token(Resource):
|
||||
|
||||
if not tenant:
|
||||
current_app.logger.error(f"Tenant not found: {tenant_id}")
|
||||
return {'message': "Tenant not found"}, 404
|
||||
return {'message': f"Authentication invalid for tenant {tenant_id}"}, 404
|
||||
|
||||
try:
|
||||
decrypted_api_key = simple_encryption.decrypt_api_key(tenant.encrypted_api_key)
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Error decrypting API key: {e}")
|
||||
return {'message': "Internal server error"}, 500
|
||||
projects = TenantProject.query.filter_by(
|
||||
tenant_id=tenant_id,
|
||||
active=True
|
||||
).all()
|
||||
|
||||
if api_key != decrypted_api_key:
|
||||
current_app.logger.error(f"Invalid API key for tenant: {tenant_id}")
|
||||
# Find project with matching API key
|
||||
matching_project = None
|
||||
for project in projects:
|
||||
try:
|
||||
decrypted_key = simple_encryption.decrypt_api_key(project.encrypted_api_key)
|
||||
if decrypted_key == api_key:
|
||||
matching_project = project
|
||||
break
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Error decrypting API key for project {project.id}: {e}")
|
||||
continue
|
||||
|
||||
if not matching_project:
|
||||
current_app.logger.error(f"Project for given API key not found for Tenant: {tenant_id}")
|
||||
return {'message': "Invalid API key"}, 401
|
||||
|
||||
if "DOCAPI" not in matching_project.services:
|
||||
current_app.logger.error(f"Service DOCAPI not authorized for Project {matching_project.name} "
|
||||
f"for Tenant: {tenant_id}")
|
||||
return {'message': f"Service DOCAPI not authorized for Project {matching_project.name}"}, 403
|
||||
|
||||
# Get the JWT_ACCESS_TOKEN_EXPIRES setting from the app config
|
||||
expires_delta = current_app.config.get('JWT_ACCESS_TOKEN_EXPIRES', timedelta(minutes=15))
|
||||
|
||||
|
||||
@@ -10,9 +10,10 @@ from werkzeug.utils import secure_filename
|
||||
from common.utils.document_utils import (
|
||||
create_document_stack, process_url, start_embedding_task,
|
||||
validate_file_type, EveAIInvalidLanguageException, EveAIDoubleURLException, EveAIUnsupportedFileType,
|
||||
process_multiple_urls, get_documents_list, edit_document, refresh_document, edit_document_version,
|
||||
get_documents_list, edit_document, refresh_document, edit_document_version,
|
||||
refresh_document_with_info
|
||||
)
|
||||
from common.utils.eveai_exceptions import EveAIException
|
||||
|
||||
|
||||
def validate_date(date_str):
|
||||
@@ -212,14 +213,23 @@ class DocumentResource(Resource):
|
||||
@document_ns.doc('edit_document')
|
||||
@document_ns.expect(edit_document_model)
|
||||
@document_ns.response(200, 'Document updated successfully')
|
||||
@document_ns.response(400, 'Validation Error')
|
||||
@document_ns.response(404, 'Document not found')
|
||||
@document_ns.response(500, 'Internal Server Error')
|
||||
def put(self, document_id):
|
||||
"""Edit a document"""
|
||||
data = request.json
|
||||
updated_doc, error = edit_document(document_id, data['name'], data.get('valid_from'), data.get('valid_to'))
|
||||
if updated_doc:
|
||||
return {'message': f'Document {updated_doc.id} updated successfully'}, 200
|
||||
else:
|
||||
return {'message': f'Error updating document: {error}'}, 400
|
||||
try:
|
||||
current_app.logger.debug(f'Editing document {document_id}')
|
||||
data = request.json
|
||||
tenant_id = get_jwt_identity()
|
||||
updated_doc, error = edit_document(tenant_id, document_id, data.get('name', None),
|
||||
data.get('valid_from', None), data.get('valid_to', None))
|
||||
if updated_doc:
|
||||
return {'message': f'Document {updated_doc.id} updated successfully'}, 200
|
||||
else:
|
||||
return {'message': f'Error updating document: {error}'}, 400
|
||||
except EveAIException as e:
|
||||
return e.to_dict(), e.status_code
|
||||
|
||||
@jwt_required()
|
||||
@document_ns.doc('refresh_document')
|
||||
@@ -249,7 +259,8 @@ class DocumentVersionResource(Resource):
|
||||
def put(self, version_id):
|
||||
"""Edit a document version"""
|
||||
data = request.json
|
||||
updated_version, error = edit_document_version(version_id, data['user_context'], data.get('catalog_properties'))
|
||||
tenant_id = get_jwt_identity()
|
||||
updated_version, error = edit_document_version(tenant_id, version_id, data['user_context'], data.get('catalog_properties'))
|
||||
if updated_version:
|
||||
return {'message': f'Document Version {updated_version.id} updated successfully'}, 200
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user