diff --git a/pyproject.toml b/pyproject.toml index fa23250..9749397 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,12 +5,13 @@ description = "Add your description here" readme = "README.md" requires-python = ">=3.13" dependencies = [ - "boto3==1.26.137", - "flask==2.2.2", - "flask-limiter==3.8.0", + "boto3==1.39.8", + "flask==3.1.1", + "flask-limiter==3.11.0", "flask-recaptcha==0.4.2", - "gunicorn==20.1.0", - "jinja2==3.0.3", - "python-dotenv==0.21.0", - "werkzeug==2.2.2", + "gunicorn==23.0.0", + "jinja2==3.1.6", + "python-dotenv==1.1.1", + "requests==2.32.4", + "werkzeug==3.1.3", ] diff --git a/server.py b/server.py index 3145739..cb0bc64 100644 --- a/server.py +++ b/server.py @@ -18,9 +18,9 @@ load_dotenv() class Config: - MAX_CONTENT_LENGTH = 15 * 1024 * 1024 # 15 MB + MAX_CONTENT_LENGTH = 40 * 1024 * 1024 # 40MB - this is the SES limit and is greated than the 20MB limit imposed in the dropzone/frontend to allow for PGP overhead EMAIL_DOMAIN = "@ethereum.org" - DEFAULT_RECIPIENT_EMAIL = "kyc@ethereum.org" + DEFAULT_RECIPIENT_EMAIL = os.getenv('DEFAULT_RECIPIENT_EMAIL', 'kyc@ethereum.org') NUMBER_OF_ATTACHMENTS = int(os.getenv('NUMBEROFATTACHMENTS', 10)) SECRET_KEY = os.getenv('SECRET_KEY', 'you-should-set-a-secret-key') @@ -134,39 +134,63 @@ def validate_turnstile(turnstile_response): def send_email(message): """ - Sends the email using AWS SES and logs detailed information for debugging. + Sends the email using AWS SES V2 and logs detailed information for debugging. """ try: - # Send the email - response = ses_client.send_raw_email( - Source=message['From'], - Destinations=[message['To']], - RawMessage={ - 'Data': message.as_string() + # Convert MIME message to bytes for SES V2 + raw_message_data = message.as_string().encode('utf-8') + + # Check message size before sending (AWS SES limit is 40MB) + message_size_mb = len(raw_message_data) / (1024 * 1024) + if message_size_mb > 40: + logging.error(f'Email message size ({message_size_mb:.2f} MB) exceeds AWS SES limit of 40MB') + raise ValueError(f'Error: Email message is too large ({message_size_mb:.2f} MB). AWS SES has a 40MB limit. Please reduce the size of attachments.') + + logging.info(f'Sending email with size: {message_size_mb:.2f} MB') + + # Send the email using SES V2 + response = ses_client.send_email( + FromEmailAddress=message['From'], + Destination={ + 'ToAddresses': [message['To']] + }, + Content={ + 'Raw': { + 'Data': raw_message_data + } } ) # Log the response message_id = response['MessageId'] - logging.info('AWS SES email sent successfully. MessageId: %s', message_id) + logging.info('AWS SES V2 email sent successfully. MessageId: %s', message_id) except ClientError as e: error_code = e.response['Error']['Code'] error_message = e.response['Error']['Message'] - logging.error('AWS SES error: Code=%s, Message=%s', error_code, error_message) + logging.error('AWS SES V2 error: Code=%s, Message=%s', error_code, error_message) # Provide user-friendly error messages - if error_code == 'MessageRejected': + if error_code == '413' or error_code == 'RequestEntityTooLarge': + # Log the message size for debugging + message_size_mb = len(raw_message_data) / (1024 * 1024) + logging.error(f'Email message size: {message_size_mb:.2f} MB') + raise ValueError('Error: Email message is too large. AWS SES has a 40MB limit for raw messages. Please reduce the size of attachments.') + elif error_code == 'MessageRejected': raise ValueError('Error: Email was rejected by AWS SES. Please check the email configuration.') elif error_code == 'MailFromDomainNotVerified': raise ValueError('Error: The sender email domain is not verified in AWS SES.') elif error_code == 'ConfigurationSetDoesNotExist': raise ValueError('Error: AWS SES configuration error.') + elif error_code == 'AccountSuspendedException': + raise ValueError('Error: AWS SES account is suspended.') + elif error_code == 'SendingPausedException': + raise ValueError('Error: AWS SES sending is paused for this account.') else: raise ValueError(f'Error: Failed to send email. {error_message}') except Exception as e: - logging.error('Error sending email via AWS SES: %s', str(e)) + logging.error('Error sending email via AWS SES V2: %s', str(e)) raise @@ -366,9 +390,9 @@ def send_identifier_to_kissflow(grant_id, legal_identifier): AWS_REGION = os.environ['AWS_REGION'] FROMEMAIL = os.environ['SES_FROM_EMAIL'] -# Initialize AWS SES client +# Initialize AWS SES V2 client ses_client = boto3.client( - 'ses', + 'sesv2', region_name=AWS_REGION, aws_access_key_id=AWS_ACCESS_KEY_ID, aws_secret_access_key=AWS_SECRET_ACCESS_KEY @@ -377,8 +401,6 @@ def send_identifier_to_kissflow(grant_id, legal_identifier): app = Flask(__name__) app.config.from_object(Config) - - # Initialize rate limiting limiter = Limiter(get_forwarded_address, app=app, default_limits=["200 per day", "50 per hour"]) @@ -388,6 +410,15 @@ def send_identifier_to_kissflow(grant_id, legal_identifier): logging.basicConfig(filename=log_file, level=logging.INFO) else: logging.basicConfig(level=logging.INFO) + +# DEBUG: Print Config values on startup +logging.info("=== DEBUG: Config Values on Startup ===") +logging.info(f"MAX_CONTENT_LENGTH: {Config.MAX_CONTENT_LENGTH}") +logging.info(f"EMAIL_DOMAIN: {Config.EMAIL_DOMAIN}") +logging.info(f"DEFAULT_RECIPIENT_EMAIL: {Config.DEFAULT_RECIPIENT_EMAIL}") +logging.info(f"NUMBER_OF_ATTACHMENTS: {Config.NUMBER_OF_ATTACHMENTS}") +logging.info(f"SECRET_KEY: {'[SET]' if Config.SECRET_KEY != 'you-should-set-a-secret-key' else '[USING DEFAULT - PLEASE SET!]'}") +logging.info("=====================================") @app.route('/', methods=['GET']) def index(): diff --git a/static/js/app.js b/static/js/app.js index 63a00af..4623413 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -23,7 +23,7 @@ function hideError() { } Dropzone.options.dropzoneArea = { - maxFilesize: 15, // Max file size per file in MB + maxFilesize: 20, // Max file size per file in MB maxFiles: 10, // Max number of files url: '/fake', paramName: 'attachment', @@ -31,7 +31,7 @@ Dropzone.options.dropzoneArea = { autoQueue: false, addRemoveLinks: true, uploadMultiple: true, - dictDefaultMessage: 'Drag & drop your files here - or click to browse. You can attach multiple files, up to a total of 15 MB.', + dictDefaultMessage: 'Drag & drop your files here - or click to browse. You can attach multiple files, up to a total of 20MB.', dictFileTooBig: 'File is too big ({{filesize}}MB). Max filesize: {{maxFilesize}}MB.', dictMaxFilesExceeded: 'You can only upload a maximum of {{maxFiles}} files.', init: function() { @@ -41,9 +41,9 @@ Dropzone.options.dropzoneArea = { hideError(); // Clear any existing errors // Check individual file size - if (file.size > 15 * 1024 * 1024) { + if (file.size > 20 * 1024 * 1024) { this.removeFile(file); - showError(`Error: File "${file.name}" is too large (${(file.size / 1024 / 1024).toFixed(2)}MB). Maximum file size is 15MB.`); + showError(`Error: File "${file.name}" is too large (${(file.size / 1024 / 1024).toFixed(2)}MB). Maximum file size is 20MB.`); return; } @@ -52,10 +52,10 @@ Dropzone.options.dropzoneArea = { return total + f.size; }, 0); - // If the total added file size is greater than 15 MB, remove the file - if (totalSize > 15 * 1024 * 1024) { + // If the total added file size is greater than 20 MB, remove the file + if (totalSize > 20 * 1024 * 1024) { this.removeFile(file); - showError(`Error: Total file size would exceed the 15MB limit. Current total: ${(totalSize / 1024 / 1024).toFixed(2)}MB`); + showError(`Error: Total file size would exceed the 20MB limit. Current total: ${(totalSize / 1024 / 1024).toFixed(2)}MB`); } }); @@ -151,16 +151,6 @@ document.addEventListener('DOMContentLoaded', function() { // Trigger change event on page load to set initial state recipient.dispatchEvent(new Event('change')); - // Redirect clicks on a greed button - const addFileButton = document.getElementById('add-file-button'); - addFileButton.addEventListener('click', (event) => { - // Get a reference to the file input element used by Dropzone.js - var fileInput = document.querySelector(".dz-hidden-input"); - - // Simulate a click event on the file input element - fileInput.click(); - }); - // Multi file upload meets encryption document.forms[0].addEventListener("submit", function(evt) { evt.preventDefault(); @@ -189,15 +179,15 @@ document.addEventListener('DOMContentLoaded', function() { return total + file.size; }, 0); - if (totalSize > 15 * 1024 * 1024) { - showError(`Error: Total file size exceeds the 15MB limit. Current total: ${(totalSize / 1024 / 1024).toFixed(2)}MB`); + if (totalSize > 20 * 1024 * 1024) { + showError(`Error: Total file size exceeds the 20MB limit. Current total: ${(totalSize / 1024 / 1024).toFixed(2)}MB`); return false; } // Check individual file sizes for (let i = 0; i < selectedFiles.length; i++) { - if (selectedFiles[i].size > 15 * 1024 * 1024) { - showError(`Error: File "${selectedFiles[i].name}" is too large (${(selectedFiles[i].size / 1024 / 1024).toFixed(2)}MB). Maximum file size is 15MB.`); + if (selectedFiles[i].size > 20 * 1024 * 1024) { + showError(`Error: File "${selectedFiles[i].name}" is too large (${(selectedFiles[i].size / 1024 / 1024).toFixed(2)}MB). Maximum file size is 20MB.`); return false; } } diff --git a/templates/413.html b/templates/413.html index a862104..cbe4b4d 100644 --- a/templates/413.html +++ b/templates/413.html @@ -1,4 +1,4 @@ {% extends "layout.html" %} {% block body %} - Error: File size is too big to process. File size must be below 15Mb. + Error: File size is too big to process. File size must be below 20Mb. {% endblock %} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 6b9cdd6..f7509db 100644 --- a/templates/index.html +++ b/templates/index.html @@ -10,7 +10,7 @@ {% endblock %} {% block body %} {% if notice %}
{{ notice }}
{% endif %} -