Skip to content

Commit 5ad4a14

Browse files
authored
Merge pull request #13 from ethereum/ses-hotfix
SES Hotfix
2 parents 0d86851 + df1c2c3 commit 5ad4a14

File tree

6 files changed

+118
-92
lines changed

6 files changed

+118
-92
lines changed

pyproject.toml

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ description = "Add your description here"
55
readme = "README.md"
66
requires-python = ">=3.13"
77
dependencies = [
8-
"boto3==1.26.137",
9-
"flask==2.2.2",
10-
"flask-limiter==3.8.0",
8+
"boto3==1.39.8",
9+
"flask==3.1.1",
10+
"flask-limiter==3.11.0",
1111
"flask-recaptcha==0.4.2",
12-
"gunicorn==20.1.0",
13-
"jinja2==3.0.3",
14-
"python-dotenv==0.21.0",
15-
"werkzeug==2.2.2",
12+
"gunicorn==23.0.0",
13+
"jinja2==3.1.6",
14+
"python-dotenv==1.1.1",
15+
"requests==2.32.4",
16+
"werkzeug==3.1.3",
1617
]

server.py

Lines changed: 48 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818
load_dotenv()
1919

2020
class Config:
21-
MAX_CONTENT_LENGTH = 15 * 1024 * 1024 # 15 MB
21+
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
2222
EMAIL_DOMAIN = "@ethereum.org"
23-
DEFAULT_RECIPIENT_EMAIL = "[email protected]"
23+
DEFAULT_RECIPIENT_EMAIL = os.getenv('DEFAULT_RECIPIENT_EMAIL', '[email protected]')
2424
NUMBER_OF_ATTACHMENTS = int(os.getenv('NUMBEROFATTACHMENTS', 10))
2525
SECRET_KEY = os.getenv('SECRET_KEY', 'you-should-set-a-secret-key')
2626

@@ -134,39 +134,63 @@ def validate_turnstile(turnstile_response):
134134

135135
def send_email(message):
136136
"""
137-
Sends the email using AWS SES and logs detailed information for debugging.
137+
Sends the email using AWS SES V2 and logs detailed information for debugging.
138138
"""
139139
try:
140-
# Send the email
141-
response = ses_client.send_raw_email(
142-
Source=message['From'],
143-
Destinations=[message['To']],
144-
RawMessage={
145-
'Data': message.as_string()
140+
# Convert MIME message to bytes for SES V2
141+
raw_message_data = message.as_string().encode('utf-8')
142+
143+
# Check message size before sending (AWS SES limit is 40MB)
144+
message_size_mb = len(raw_message_data) / (1024 * 1024)
145+
if message_size_mb > 40:
146+
logging.error(f'Email message size ({message_size_mb:.2f} MB) exceeds AWS SES limit of 40MB')
147+
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.')
148+
149+
logging.info(f'Sending email with size: {message_size_mb:.2f} MB')
150+
151+
# Send the email using SES V2
152+
response = ses_client.send_email(
153+
FromEmailAddress=message['From'],
154+
Destination={
155+
'ToAddresses': [message['To']]
156+
},
157+
Content={
158+
'Raw': {
159+
'Data': raw_message_data
160+
}
146161
}
147162
)
148163

149164
# Log the response
150165
message_id = response['MessageId']
151-
logging.info('AWS SES email sent successfully. MessageId: %s', message_id)
166+
logging.info('AWS SES V2 email sent successfully. MessageId: %s', message_id)
152167

153168
except ClientError as e:
154169
error_code = e.response['Error']['Code']
155170
error_message = e.response['Error']['Message']
156-
logging.error('AWS SES error: Code=%s, Message=%s', error_code, error_message)
171+
logging.error('AWS SES V2 error: Code=%s, Message=%s', error_code, error_message)
157172

158173
# Provide user-friendly error messages
159-
if error_code == 'MessageRejected':
174+
if error_code == '413' or error_code == 'RequestEntityTooLarge':
175+
# Log the message size for debugging
176+
message_size_mb = len(raw_message_data) / (1024 * 1024)
177+
logging.error(f'Email message size: {message_size_mb:.2f} MB')
178+
raise ValueError('Error: Email message is too large. AWS SES has a 40MB limit for raw messages. Please reduce the size of attachments.')
179+
elif error_code == 'MessageRejected':
160180
raise ValueError('Error: Email was rejected by AWS SES. Please check the email configuration.')
161181
elif error_code == 'MailFromDomainNotVerified':
162182
raise ValueError('Error: The sender email domain is not verified in AWS SES.')
163183
elif error_code == 'ConfigurationSetDoesNotExist':
164184
raise ValueError('Error: AWS SES configuration error.')
185+
elif error_code == 'AccountSuspendedException':
186+
raise ValueError('Error: AWS SES account is suspended.')
187+
elif error_code == 'SendingPausedException':
188+
raise ValueError('Error: AWS SES sending is paused for this account.')
165189
else:
166190
raise ValueError(f'Error: Failed to send email. {error_message}')
167191

168192
except Exception as e:
169-
logging.error('Error sending email via AWS SES: %s', str(e))
193+
logging.error('Error sending email via AWS SES V2: %s', str(e))
170194
raise
171195

172196

@@ -366,9 +390,9 @@ def send_identifier_to_kissflow(grant_id, legal_identifier):
366390
AWS_REGION = os.environ['AWS_REGION']
367391
FROMEMAIL = os.environ['SES_FROM_EMAIL']
368392

369-
# Initialize AWS SES client
393+
# Initialize AWS SES V2 client
370394
ses_client = boto3.client(
371-
'ses',
395+
'sesv2',
372396
region_name=AWS_REGION,
373397
aws_access_key_id=AWS_ACCESS_KEY_ID,
374398
aws_secret_access_key=AWS_SECRET_ACCESS_KEY
@@ -377,8 +401,6 @@ def send_identifier_to_kissflow(grant_id, legal_identifier):
377401
app = Flask(__name__)
378402
app.config.from_object(Config)
379403

380-
381-
382404
# Initialize rate limiting
383405
limiter = Limiter(get_forwarded_address, app=app, default_limits=["200 per day", "50 per hour"])
384406

@@ -388,6 +410,15 @@ def send_identifier_to_kissflow(grant_id, legal_identifier):
388410
logging.basicConfig(filename=log_file, level=logging.INFO)
389411
else:
390412
logging.basicConfig(level=logging.INFO)
413+
414+
# DEBUG: Print Config values on startup
415+
logging.info("=== DEBUG: Config Values on Startup ===")
416+
logging.info(f"MAX_CONTENT_LENGTH: {Config.MAX_CONTENT_LENGTH}")
417+
logging.info(f"EMAIL_DOMAIN: {Config.EMAIL_DOMAIN}")
418+
logging.info(f"DEFAULT_RECIPIENT_EMAIL: {Config.DEFAULT_RECIPIENT_EMAIL}")
419+
logging.info(f"NUMBER_OF_ATTACHMENTS: {Config.NUMBER_OF_ATTACHMENTS}")
420+
logging.info(f"SECRET_KEY: {'[SET]' if Config.SECRET_KEY != 'you-should-set-a-secret-key' else '[USING DEFAULT - PLEASE SET!]'}")
421+
logging.info("=====================================")
391422

392423
@app.route('/', methods=['GET'])
393424
def index():

static/js/app.js

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ function hideError() {
2323
}
2424

2525
Dropzone.options.dropzoneArea = {
26-
maxFilesize: 15, // Max file size per file in MB
26+
maxFilesize: 20, // Max file size per file in MB
2727
maxFiles: 10, // Max number of files
2828
url: '/fake',
2929
paramName: 'attachment',
3030
autoProcessQueue: false,
3131
autoQueue: false,
3232
addRemoveLinks: true,
3333
uploadMultiple: true,
34-
dictDefaultMessage: 'Drag & drop your files here - or click to browse. You can attach multiple files, up to a total of 15 MB.',
34+
dictDefaultMessage: 'Drag & drop your files here - or click to browse. You can attach multiple files, up to a total of 20MB.',
3535
dictFileTooBig: 'File is too big ({{filesize}}MB). Max filesize: {{maxFilesize}}MB.',
3636
dictMaxFilesExceeded: 'You can only upload a maximum of {{maxFiles}} files.',
3737
init: function() {
@@ -41,9 +41,9 @@ Dropzone.options.dropzoneArea = {
4141
hideError(); // Clear any existing errors
4242

4343
// Check individual file size
44-
if (file.size > 15 * 1024 * 1024) {
44+
if (file.size > 20 * 1024 * 1024) {
4545
this.removeFile(file);
46-
showError(`Error: File "${file.name}" is too large (${(file.size / 1024 / 1024).toFixed(2)}MB). Maximum file size is 15MB.`);
46+
showError(`Error: File "${file.name}" is too large (${(file.size / 1024 / 1024).toFixed(2)}MB). Maximum file size is 20MB.`);
4747
return;
4848
}
4949

@@ -52,10 +52,10 @@ Dropzone.options.dropzoneArea = {
5252
return total + f.size;
5353
}, 0);
5454

55-
// If the total added file size is greater than 15 MB, remove the file
56-
if (totalSize > 15 * 1024 * 1024) {
55+
// If the total added file size is greater than 20 MB, remove the file
56+
if (totalSize > 20 * 1024 * 1024) {
5757
this.removeFile(file);
58-
showError(`Error: Total file size would exceed the 15MB limit. Current total: ${(totalSize / 1024 / 1024).toFixed(2)}MB`);
58+
showError(`Error: Total file size would exceed the 20MB limit. Current total: ${(totalSize / 1024 / 1024).toFixed(2)}MB`);
5959
}
6060
});
6161

@@ -151,16 +151,6 @@ document.addEventListener('DOMContentLoaded', function() {
151151
// Trigger change event on page load to set initial state
152152
recipient.dispatchEvent(new Event('change'));
153153

154-
// Redirect clicks on a greed button
155-
const addFileButton = document.getElementById('add-file-button');
156-
addFileButton.addEventListener('click', (event) => {
157-
// Get a reference to the file input element used by Dropzone.js
158-
var fileInput = document.querySelector(".dz-hidden-input");
159-
160-
// Simulate a click event on the file input element
161-
fileInput.click();
162-
});
163-
164154
// Multi file upload meets encryption
165155
document.forms[0].addEventListener("submit", function(evt) {
166156
evt.preventDefault();
@@ -189,15 +179,15 @@ document.addEventListener('DOMContentLoaded', function() {
189179
return total + file.size;
190180
}, 0);
191181

192-
if (totalSize > 15 * 1024 * 1024) {
193-
showError(`Error: Total file size exceeds the 15MB limit. Current total: ${(totalSize / 1024 / 1024).toFixed(2)}MB`);
182+
if (totalSize > 20 * 1024 * 1024) {
183+
showError(`Error: Total file size exceeds the 20MB limit. Current total: ${(totalSize / 1024 / 1024).toFixed(2)}MB`);
194184
return false;
195185
}
196186

197187
// Check individual file sizes
198188
for (let i = 0; i < selectedFiles.length; i++) {
199-
if (selectedFiles[i].size > 15 * 1024 * 1024) {
200-
showError(`Error: File "${selectedFiles[i].name}" is too large (${(selectedFiles[i].size / 1024 / 1024).toFixed(2)}MB). Maximum file size is 15MB.`);
189+
if (selectedFiles[i].size > 20 * 1024 * 1024) {
190+
showError(`Error: File "${selectedFiles[i].name}" is too large (${(selectedFiles[i].size / 1024 / 1024).toFixed(2)}MB). Maximum file size is 20MB.`);
201191
return false;
202192
}
203193
}

templates/413.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
{% extends "layout.html" %}
22
{% block body %}
3-
<b>Error:</b> File size is too big to process. File size must be below 15Mb.
3+
<b>Error:</b> File size is too big to process. File size must be below 20Mb.
44
{% endblock %}

templates/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
{% endblock %}
1111
{% block body %}
1212
{% if notice %}<p class="notice"><b>{{ notice }}</b></p>{% endif %}
13-
<form id="submission-form" class="pure-form pure-form-stacked" name="contact" method="post" action="/">
13+
<form id="submission-form" class="pure-form pure-form-stacked" name="contact" method="post" action="/submit-encrypted-data">
1414
<fieldset>
1515
<legend>Secure Submission Form</legend>
1616
<span class="pure-form-message">

0 commit comments

Comments
 (0)