Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]
65 changes: 48 additions & 17 deletions server.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')

Expand Down Expand Up @@ -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


Expand Down Expand Up @@ -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
Expand All @@ -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"])

Expand All @@ -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():
Expand Down
32 changes: 11 additions & 21 deletions static/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ 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',
autoProcessQueue: false,
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() {
Expand All @@ -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;
}

Expand All @@ -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`);
}
});

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
}
}
Expand Down
2 changes: 1 addition & 1 deletion templates/413.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% extends "layout.html" %}
{% block body %}
<b>Error:</b> File size is too big to process. File size must be below 15Mb.
<b>Error:</b> File size is too big to process. File size must be below 20Mb.
{% endblock %}
2 changes: 1 addition & 1 deletion templates/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
{% endblock %}
{% block body %}
{% if notice %}<p class="notice"><b>{{ notice }}</b></p>{% endif %}
<form id="submission-form" class="pure-form pure-form-stacked" name="contact" method="post" action="/">
<form id="submission-form" class="pure-form pure-form-stacked" name="contact" method="post" action="/submit-encrypted-data">
<fieldset>
<legend>Secure Submission Form</legend>
<span class="pure-form-message">
Expand Down
Loading